Я вижу несколько постов, где важность обработки исключений в центральном местоположении или на границе процесса подчеркивалась как хорошая практика, а не мусор каждого блока кода вокруг try / catch. Я твердо верю, что большинство из нас понимают важность этого, однако я вижу, что люди по-прежнему используют анти-паттерн catch-log-rethrow главным образом потому, что для облегчения устранения неполадок во время любого исключения они хотят регистрировать больше информации, специфичной для контекста (пример: параметры метода прошло), и путь заключается в том, чтобы обернуть метод вокруг try / catch / log / rethrow.
public static bool DoOperation(int num1, int num2)
{
try
{
/* do some work with num1 and num2 */
}
catch (Exception ex)
{
logger.log("error occured while number 1 = {num1} and number 2 = {num2}");
throw;
}
}
Есть ли правильный способ достичь этого, сохраняя при этом хорошую практику обработки исключений? Я слышал об AOP-фреймворке, таком как PostSharp, но хотел бы знать, есть ли какие-либо недостатки или большие затраты производительности, связанные с этими AOP-фреймворками.
Благодарность!
источник
Ответы:
Проблема не в локальном блоке перехвата, проблема в журнале и перебрасывании . Либо обработайте исключение, либо заключите его в новое исключение, которое добавляет дополнительный контекст, и выбросьте его. В противном случае вы столкнетесь с несколькими дублирующимися записями журнала для одного и того же исключения.
Идея заключается в том, чтобы расширить возможности отладки вашего приложения.
Пример № 1: справиться с этим
Если вы обработаете исключение, вы можете легко понизить важность записи в журнале исключений, и нет никаких причин фильтровать это исключение в цепочке. С этим уже разобрались.
Обработка исключения может включать информирование пользователей о возникшей проблеме, регистрацию события или просто его игнорирование.
ПРИМЕЧАНИЕ: если вы намеренно игнорируете исключение, я рекомендую предоставить комментарий в пустом предложении catch, в котором четко указано, почему. Это позволяет будущим сопровождающим знать, что это не было ошибкой или ленивым программированием. Пример:
Пример № 2: Добавить дополнительный контекст и бросить
Добавление дополнительного контекста (например, номера строки и имени файла в коде синтаксического анализа) может помочь улучшить возможность отладки входных файлов - при условии, что проблема существует. Это особый случай, поэтому повторная упаковка исключения в «ApplicationException» только для того, чтобы переименовать его, не поможет вам отладить. Убедитесь, что вы добавили дополнительную информацию.
Пример № 3: ничего не делать, за исключением
В этом последнем случае вы просто оставляете исключение, не касаясь его. Обработчик исключений на самом внешнем уровне может обрабатывать регистрацию. Это
finally
предложение используется для того, чтобы убедиться, что все ресурсы, необходимые для вашего метода, очищены, но это не место для регистрации того, что было сгенерировано исключение.источник
Я не верю, что локальные уловы являются анти-паттерном, на самом деле, если я правильно помню, они на самом деле применяются в Java!
При реализации обработки ошибок для меня ключевым является общая стратегия. Вам может понадобиться фильтр, который перехватывает все исключения на границе сервиса, вы можете захотеть перехватывать их вручную - оба они хороши, если есть общая стратегия, которая будет соответствовать стандартам кодирования вашей команды.
Лично мне нравится отлавливать ошибки внутри функции, когда я могу выполнить одно из следующих действий:
Если это не один из этих случаев, я не добавляю локальный try / catch. Если это так, в зависимости от сценария я могу обработать исключение (например, метод TryX, который возвращает false) или перебросить, чтобы исключение было обработано глобальной стратегией.
Например:
Или пример повторного броска:
Затем вы ловите ошибку в верхней части стека и представляете пользователю удобное сообщение.
Какой бы подход вы ни выбрали, всегда стоит создавать модульные тесты для этих сценариев, чтобы вы могли быть уверены, что функциональность не изменится и не нарушит поток проекта на более позднем этапе.
Вы не упомянули, на каком языке вы работаете, но были разработчиком .NET и видели это слишком много раз, чтобы не упоминать об этом.
НЕ ПИШИТЕ:
Использование:
Первый сбрасывает трассировку стека и делает ваш улов верхнего уровня совершенно бесполезным!
TLDR
Локальный отлов не является анти-паттерном, он часто может быть частью дизайна и может помочь добавить дополнительный контекст к ошибке.
источник
Это во многом зависит от языка. Например, C ++ не предлагает трассировки стека в сообщениях об ошибках исключений, поэтому может помочь отслеживание исключения с помощью частых перехватов-повторений-повторов. Напротив, Java и подобные языки предлагают очень хорошие трассировки стека, хотя формат этих трасс стека может быть не очень настраиваемым. Поймать и повторно выбросить исключения в этих языках совершенно бессмысленно, если вы действительно не можете добавить какой-то важный контекст (например, связать низкоуровневое исключение SQL с контекстом операции бизнес-логики).
Любая стратегия обработки ошибок, реализованная с помощью отражения, почти всегда менее эффективна, чем встроенная в язык функциональность. Кроме того, повсеместное ведение журналов неизбежно снижает производительность. Таким образом, вам необходимо сбалансировать поток получаемой информации с другими требованиями к этому программному обеспечению. Тем не менее, такие решения, как PostSharp, которые построены на инструментальных средствах уровня компилятора, обычно работают намного лучше, чем отражение во время выполнения.
Я лично считаю, что регистрация всего не полезна, потому что она включает в себя множество ненужной информации. Поэтому я скептически отношусь к автоматизированным решениям. При хорошей структуре ведения журнала может быть достаточно иметь согласованное руководство по кодированию, в котором обсуждается, какую информацию вы хотите регистрировать и как эту информацию следует форматировать. Затем вы можете добавить запись, где это имеет значение.
Вход в бизнес-логику гораздо важнее, чем вход в служебные функции. А сбор стековых следов реальных отчетов о сбоях (для которых требуется только регистрация на верхнем уровне процесса) позволяет вам находить области кода, где регистрация будет иметь наибольшее значение.
источник
Когда я вижу
try/catch/log
в каждом методе, это вызывает опасения, что разработчики понятия не имели, что может или не могло произойти в их приложении, предполагали худшее и упреждающе регистрировали все везде из-за всех ошибок, которые они ожидали.Это признак того, что модульного и интеграционного тестирования недостаточно, и разработчики привыкли обходить большое количество кода в отладчике и надеются, что каким-то образом большое количество журналов позволит им развернуть ошибочный код в тестовой среде и найти проблемы, посмотрев на журналы.
Код, который бросает исключения, может быть более полезным, чем избыточный код, который перехватывает и регистрирует исключения. Если вы генерируете исключение со значимым сообщением, когда метод получает неожиданный аргумент (и регистрируете его на границе службы), это гораздо более полезно, чем немедленная регистрация исключения, вызванного как побочный эффект недопустимого аргумента, и необходимость угадывать, что его вызвало ,
Нули являются примером. Если вы получаете значение в качестве аргумента или результата вызова метода, и оно не должно быть нулевым, тогда генерируйте исключение. Не просто записать получившиеся
NullReferenceException
брошенные пять строк позже из-за нулевого значения. В любом случае вы получаете исключение, но один говорит вам что-то, а другой заставляет вас искать.Как говорят другие, лучше регистрировать исключения на границе службы или всякий раз, когда исключение не перебрасывается, потому что оно обрабатывается изящно. Самое важное различие между чем-то и ничем. Если ваши исключения зарегистрированы в одном месте, к которому легко добраться, вы найдете необходимую информацию, когда вам это нужно.
источник
Если вам нужно записать контекстную информацию, которой еще нет в исключении, вы заключите ее в новое исключение и предоставите исходное исключение как
InnerException
. Таким образом, у вас все еще сохраняется исходная трассировка стека. Так:Второй параметр
Exception
конструктора обеспечивает внутреннее исключение. Затем вы можете записать все исключения в одном месте, и вы все равно получите полную трассировку стека и контекстную информацию в той же записи журнала.Возможно, вы захотите использовать пользовательский класс исключений, но смысл тот же.
try / catch / log / rethrow - беспорядок, потому что он приведет к запутанным журналам - например, что если в другом потоке произойдет другое исключение между записью контекстной информации и фактическим исключением в обработчике верхнего уровня? Попытка / ловить / выбросить - это нормально, если новое исключение добавляет информацию к оригиналу.
источник
Само исключение должно предоставлять всю информацию, необходимую для правильной регистрации, включая сообщение, код ошибки и что нет. Поэтому не должно быть необходимости перехватывать исключение только для того, чтобы сбросить его или выдать другое исключение.
Часто вы видите шаблон нескольких исключений, перехваченных и переброшенных как общее исключение, таких как перехват исключений DatabaseConnectionException, InvalidQueryException и InvalidSQLParameterException и повторное выбрасывание DatabaseException. Хотя, к этому, я бы сказал, что все эти конкретные исключения в первую очередь должны быть производными от DatabaseException, поэтому повторное отбрасывание не требуется.
Вы обнаружите, что удаление ненужных предложений try catch (даже тех, которые предназначены исключительно для ведения журнала) на самом деле сделает работу проще, а не сложнее. Только места в вашей программе, которые обрабатывают исключение, должны регистрировать исключение, и, если все остальное терпит неудачу, обработчик исключений в рамках всей программы для одной последней попытки зарегистрировать исключение перед грациозным выходом из программы. Исключение должно иметь полную трассировку стека, указывающую точную точку, в которой было сгенерировано исключение, поэтому часто нет необходимости обеспечивать «контекстную» регистрацию.
Тем не менее, АОП может быть быстрым решением для вас, хотя обычно это влечет за собой небольшое замедление. Я бы посоветовал вам вместо этого полностью удалить ненужные предложения try catch, в которых ничего не добавлено.
источник
try { tester.test(); } catch (NullPointerException e) { logger.error("Variable tester was null!"); }
. Трассировка стека в большинстве случаев достаточна, но при отсутствии такого типа тип ошибки обычно адекватен.