Должны ли я перехватывать исключения только для их регистрации?
Следует ли исключать исключения для ведения журнала?
public foo(..)
{
try
{
...
} catch (Exception ex) {
Logger.Error(ex);
throw;
}
}
Если у меня есть это место на каждом из моих слоев (DataAccess, Business и WebService), это означает, что исключение регистрируется несколько раз.
Имеет ли смысл делать это, если мои слои находятся в отдельных проектах, и только публичные интерфейсы имеют в них try/catch?
Зачем? Почему нет? Есть ли другой подход, который я мог бы использовать?
Ответы
Ответ 1
Определенно нет. Вы должны найти правильное место для дескриптора исключения (на самом деле что-то сделать, например, catch-and-not-rethrow), а затем зарегистрировать его. Конечно, вы можете и должны включать в себя всю трассировку стека, но после вашего предложения будет зависеть код от блоков try-catch.
Ответ 2
Если вы не собираетесь изменять исключение, вы должны только регистрироваться на уровне, на котором вы собираетесь обрабатывать ошибку, а не реконструировать ее. В противном случае ваш журнал просто имеет кучу "шума", 3 или более одного и того же сообщенного журнала, один раз на каждом уровне.
Моя лучшая практика:
- Только попробовать/поймать в публичных методах (в общем, очевидно, что если вы поймаете ловушку для конкретной ошибки, вы можете проверить ее там)
- Загрузите только пользовательский интерфейс непосредственно перед подавлением ошибки и перенаправлением на страницу/форму ошибки.
Ответ 3
Общее правило заключается в том, что вы получаете только исключение, если вы действительно можете что-то сделать. Поэтому на уровне "Бизнес" или "Данные" вы поймаете исключение только в следующей ситуации:
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 - вместо того, чтобы генерировать исключение.
Ответ 4
Хорошей практикой является переводить исключения. Не просто регистрируйте их. Если вы хотите узнать конкретную причину исключения, бросьте конкретные исключения:
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);
}
}
Ответ 5
Используйте собственные исключения, чтобы обернуть исключение inbuild. Таким образом, вы можете различать известные и неизвестные ошибки при обнаружении исключения. Это полезно, если у вас есть метод, который вызывает другие методы, которые, вероятно, бросают экскременты, чтобы реагировать на ожидаемые и неожиданные сбои.
Ответ 6
вы можете искать стандартные стили обработки исключений, но я понимаю это: обрабатывать исключения на уровне, где вы можете добавить дополнительные детали к исключению или на уровне, где вы представите исключение пользователю.
в вашем примере вы ничего не делаете, кроме как поймать исключение, зарегистрировать его и снова выбросить.. почему бы просто не поймать его на самом высоком уровне с помощью одной попытки /catch, а не внутри каждого метода, если все, что вы делаете, - это вести журнал это?
я бы справлялся с этим только на этом уровне, если бы вы собирались добавить какую-то полезную информацию в исключение, прежде чем бросать его снова - оберните исключение в новом исключении, которое вы создаете, которое имеет полезную информацию за пределами текста исключения с низким уровнем, что обычно означает мало кому угодно, без какого-либо контекста.
Ответ 7
Иногда вам нужно регистрировать данные, которые недоступны, когда обрабатывается исключение. В этом случае уместно регистрироваться только для получения этой информации.
Например (псевдокод 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
}
}
Это похоже на ретроактивное ведение журнала (моя терминология), где вы буферизируете журнал событий, но не записываете их, если нет события триггера, такого как генерируемое исключение.
Ответ 8
Если вам нужно регистрировать все исключения, то это фантастическая идея. Тем не менее, регистрация всех исключений без какой-либо другой причины не такая хорошая идея.
Ответ 9
Возможно, вы захотите зарегистрироваться на самом высоком уровне, который обычно является вашим интерфейсом пользовательского интерфейса или веб-сервисом. Регистрация нескольких раз - это отходы. Кроме того, вы хотите узнать всю историю, когда смотрите журнал.
В одном из наших приложений все наши страницы получены из объекта BasePage, и этот объект обрабатывает обработку исключений и протоколирование ошибок.
Ответ 10
Если это единственное, что он делает, я думаю, что лучше удалить try/catch из этих классов и позволить исключению быть поднятым классу, ответственному за их обработку. Таким образом, вы получаете только один журнал за исключение, предоставляя вам более четкие журналы и даже вы можете записывать стек, чтобы вы не пропустили, откуда возникло исключение.
Ответ 11
Мой метод заключается в регистрации исключений только в обработчике. "Настоящий" обработчик, так сказать. В противном случае журнал будет очень трудно читать, а код менее структурирован.
Ответ 12
Это зависит от Исключения: если этого на самом деле не должно произойти, я определенно буду регистрировать его. С другой стороны: если вы ожидаете этого исключения, вам следует подумать о дизайне приложения.
В любом случае: вы должны хотя бы попытаться указать исключение, которое хотите перебросить, поймать или занести в журнал.
public foo(..)
{
try
{
...
}
catch (NullReferenceException ex) {
DoSmth(e);
}
catch (ArgumentExcetion ex) {
DoSmth(e);
}
catch (Exception ex) {
DoSmth(e);
}
}
Ответ 13
Вы захотите войти на границу уровня. Например, если ваш бизнес-уровень может быть развернут на физически отдельной машине в n-уровневом приложении, тогда имеет смысл регистрировать и выдавать ошибку таким образом.
Таким образом, у вас есть журнал исключений на сервере, и вам не нужно прокручивать клиентские компьютеры, чтобы узнать, что произошло.
Я использую этот шаблон для бизнес-уровней приложений, использующих веб-службы Remoting или ASMX. С WCF вы можете перехватывать и регистрировать исключение, используя IErrorHandler, прикрепленный к вашему ChannelDispatcher (другой предмет целиком), поэтому вам не нужен шаблон try/catch/throw.
Ответ 14
Вам нужно разработать стратегию обработки исключений. Я не рекомендую улов и ретрол. В дополнение к избыточным записям журнала это делает код более трудным для чтения.
Рассмотрим запись в журнал конструктора для исключения. Это резервирует try/catch для исключений, из которых вы хотите восстановить; что делает код более удобным для чтения. Чтобы справиться с неожиданными или невосстановимыми исключениями, вам может потребоваться попытка/улов рядом с самым внешним слоем программы для записи диагностической информации.
Кстати, если это С++, ваш блок catch создает копию объекта исключения, который может быть потенциальным источником дополнительных проблем. Попробуйте поймать ссылку на тип исключения:
catch (const Exception& ex) { ... }
Ответ 15
This Software Rising podcast - очень хорошая справочная информация о лучших методах обработки ошибок. На самом деле есть 2 лекции.