Подтвердить что ты не робот

Почему exception.printStackTrace() считается плохой практикой?

Существует lot материал вне там, который предполагает, что печать трассировки стека исключений - это плохая практика.

например. из проверки RegexpSingleline в Checkstyle:

Эта проверка может использоваться [...], чтобы найти распространенную плохую практику, такую ​​как вызов ex.printStacktrace()

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

  • Трассировка стека никогда не должна быть видимой для конечных пользователей (для пользователей и целей безопасности)

  • Создание трассировки стека - относительно дорогостоящий процесс (хотя вряд ли это будет проблемой в большинстве "исключительных" обстоятельств)

  • Многие фреймворки протоколирования распечатывают трассировку стека (у нас нет и нет, мы не можем легко ее изменить)

  • Печать трассировки стека не является обработкой ошибок. Он должен сочетаться с другим ведением информации и обработкой исключений.

Какие еще есть причины избежать печати трассировки стека в вашем коде?

4b9b3361

Ответ 1

Throwable.printStackTrace() записывает трассировку стека в System.err PrintStream. Поток System.err и базовый стандартный поток ошибок "ошибки" процесса JVM можно перенаправить с помощью

  • вызывает System.setErr(), который изменяет пункт назначения, на который указывает System.err.
  • или путем перенаправления потока выходных данных процесса. Выходной поток ошибки может быть перенаправлен на файл/устройство
    • содержимое которого может быть проигнорировано персоналом,
    • файл/устройство может не работать с вращением журнала, вызывая, что для закрытия дескриптора открытого файла/устройства требуется перезапуск процесса, прежде чем архивировать существующее содержимое файла/устройства.
    • или файл/устройство фактически отбрасывает все записанные на него данные, как в случае с /dev/null.

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

  • если у вас нет System.err, переназначенного на протяжении всего срока службы приложения,
  • и если вам не требуется поворот журнала во время работы приложения,
  • и если принятая/разработанная практика ведения журнала приложения заключается в записи в System.err (и стандартный поток ошибок JVM).

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

Наконец, следует помнить, что вывод Throwable.printStackTrace() определенно будет чередоваться с другим контентом, записанным на System.err (и, возможно, даже System.out, если оба будут перенаправлены на тот же файл/устройство). Это раздражение (для однопоточных приложений), с которым нужно иметь дело, поскольку данные вокруг исключений нелегко разбираются в таком событии. Хуже того, очень вероятно, что многопоточное приложение создаст очень запутывающие журналы, поскольку Throwable.printStackTrace() не является потокобезопасным.

Механизм синхронизации не синхронизирует запись трассировки стека с System.err, когда несколько потоков обращаются к Throwable.printStackTrace() одновременно. Для этого необходимо, чтобы ваш код синхронизировался на мониторе, связанном с System.err (а также System.out, если целевой файл/устройство одинаков), и это довольно высокая цена, чтобы заплатить за доступ к журнальному файлу. Для примера, классы ConsoleHandler и StreamHandler отвечают за добавление записей журнала в консоль, в средство ведения журнала, предоставляемое java.util.logging; фактическая работа публикации записей журнала синхронизируется - каждый поток, который пытается опубликовать запись журнала, также должен получить блокировку на мониторе, связанную с экземпляром StreamHandler. Если вы хотите иметь такую ​​же гарантию наличия записей с чередованием без чередования с помощью System.out/System.err, вы должны обеспечить то же самое - сообщения публикуются в эти потоки сериализуемым образом.

Учитывая все вышеизложенное и очень ограниченные сценарии, в которых Throwable.printStackTrace() действительно полезен, часто оказывается, что обращение к нему является плохой практикой.


Расширение аргумента в одном из предыдущих абзацев, также плохой выбор для использования Throwable.printStackTrace в сочетании с журналом, который записывает на консоль. Это частично связано с тем, что регистратор будет синхронизироваться на другом мониторе, в то время как ваше приложение (возможно, если вы не хотите, чтобы записи с чередованием журнала) синхронизировались на другом мониторе. Аргумент также хорош, если вы используете два разных регистратора, которые пишут в тот же пункт назначения в вашем приложении.

Ответ 2

Вы касаетесь нескольких проблем здесь:

1) Трассировка стека никогда не должна быть видимой для конечных пользователей (для пользователей и целей безопасности)

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

  • Они очень неясны и нечитаемы, приложение будет выглядеть очень недружелюбным пользователем.
  • Отображение трассировки стека для конечного пользователя может привести к потенциальной угрозе безопасности. Исправьте меня, если я ошибаюсь, PHP на самом деле печатает функциональные параметры в трассировке стека - блестящие, но очень опасные - если вы получите исключение при подключении к базе данных, что вы, вероятно, будете в stacktrace?

2) Создание трассировки стека - относительно дорогостоящий процесс (хотя вряд ли это будет проблемой в большинстве "исключительных" обстоятельств)

Генерация трассировки стека происходит, когда создается или генерируется исключение (почему бросок исключения приходит с ценой), печать не так дорого. Фактически вы можете переопределить Throwable#fillInStackTrace() в своем настраиваемом исключении, что делает исключение почти таким же дешевым, как и простое выражение GOTO.

3) Многие фреймворки протоколирования будут печатать трассировку стека для вас (у нас нет и нет, мы не можем легко ее изменить)

Очень хорошая точка. Основная проблема здесь: если фреймворк регистрирует для вас исключение, ничего не делайте (но убедитесь, что он это сделал!) Если вы хотите самостоятельно регистрировать исключение, используйте фреймворк регистрации, например Logback или Log4J, чтобы не помещать их в необработанную консоль, потому что ее очень сложно контролировать.

С помощью фреймворка регистрации вы можете легко перенаправить трассировки стека в файл, консоль или даже отправить их на указанный адрес электронной почты. С жестко закодированным printStackTrace() вам нужно жить с sysout.

4) Печать трассировки стека не является обработкой ошибок. Он должен сочетаться с другим протоколированием информации и обработкой исключений.

Снова: log SQLException правильно (с полной трассировкой стека, используя фреймворк ведения журналов) и покажите хорошо: сообщение "Извините, мы в настоящее время не можем обработать ваш запрос". Вы действительно думаете, что пользователь заинтересован в причинах? Вы видели экран ошибок StackOverflow? Он очень юмористичен, но не раскрывает никаких подробностей. Однако он гарантирует пользователю, что проблема будет исследована.

Но он немедленно позвонит вам, и вам нужно будет диагностировать проблему. Таким образом, вам нужны оба: правильное ведение журнала ошибок и удобные сообщения.


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

Ответ 3

Первое, что printStackTrace() не так дорого, как вы заявляете, потому что трассировка стека заполняется, когда создается исключение.

Идея состоит в том, чтобы передать все, что идет в журналы, через фреймворк logger, чтобы можно было управлять журналом. Следовательно, вместо использования printStackTrace просто используйте что-то вроде Logger.log(msg, exception);

Ответ 4

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

Также существует тенденция подозревать, что правильная обработка исключений не выполняется, если все, что выполняется в блоке catch, равно e.printStackTrace. Неправильная обработка может означать, что в лучшем случае проблема игнорируется, а в худшем - программа, которая продолжает выполняться в undefined или в неожиданном состоянии.

Пример

Рассмотрим следующий пример:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

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

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

Во многих случаях e.printStackTrace() является признаком того, что какое-то исключение проглатывается, и обработка разрешается продолжать, как если бы никаких проблем не возникало.

Почему это стало проблемой?

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

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(Вышеупомянутый фактический try-catch автоматически сгенерированный Eclipse для обработки InterruptedException, созданного Thread.sleep.)

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

Ответ 5

Я думаю, что ваш список причин довольно всеобъемлющий.

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

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

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

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

Наконец, на более светлой ноте, Бог Совершенное Исключение.

Ответ 6

printStackTrace() выводится на консоль. В производственных условиях никто никогда не смотрит на это. Сурай прав, должен передать эту информацию регистратору.

Ответ 7

В серверных приложениях stacktrace взорвает ваш файл stdout/stderr. Он может стать все больше и больше и заполнен бесполезными данными, потому что обычно у вас нет контекста, нет метки времени и т.д.

например. catalina.out при использовании tomcat в качестве контейнера

Ответ 8

Это неплохая практика, потому что что-то "неправильно" в PrintStackTrace(), но потому, что это "запах кода". В большинстве случаев вызов PrintStackTrace() существует, потому что кому-то не удалось обработать исключение. Как только вы справляетесь с этим исключением должным образом, вам больше не нравится StackTrace.

Кроме того, отображение stacktrace на stderr обычно полезно только при отладке, а не в производстве, потому что очень часто stderr не идет нигде. Ведение журнала имеет смысл. Но просто заменяя PrintStackTrace() при регистрации, исключение все равно оставляет вас с приложением, которое провалилось, но продолжает работать, как ничего не произошло.

Ответ 9

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

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

Ответ 10

лучшее, что можно сделать

 LOG.error("-- createRssForSiteFrameworkContainer() " + e.getMessage(), e);