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

126

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

Например, из проверки RegexpSingleline в Checkstyle:

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

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

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

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

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

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

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

Крис Найт
источник
12
Как человек, которому приходится регулярно устранять неполадки, я бы никогда не пропустил печать трассировки стека, если что-то пошло не так. Да, не показывать пользователю, но да, записывать в журнал ошибок.
Пол Грайм
4
Вам действительно нужны другие причины? Я думаю, вы достаточно хорошо ответили на свой вопрос. Однако в исключительных случаях следует выводить stacktrace. :)
Neil

Ответы:

125

Throwable.printStackTrace()записывает трассировку стека в System.errPrintStream. System.errПоток и основной стандарт «ошибка» выходной поток процесса виртуальной машины Java может быть переадресован

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

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

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

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

Наконец, следует помнить, что выходные данные 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вместе с регистратором, который записывает в консоль. Частично это связано с тем, что регистратор будет синхронизироваться на другом мониторе, в то время как ваше приложение (возможно, если вы не хотите, чтобы записи журнала чередовались) синхронизировалось на другом мониторе. Этот аргумент также применим, когда вы используете в приложении два разных регистратора, которые записывают в одно и то же место назначения.

Винит Рейнольдс
источник
Отличный ответ, спасибо. Однако, хотя я согласен с тем, что в большинстве случаев это шум или не нужно, бывают случаи, когда это абсолютно критично.
Крис Найт,
1
@Chris Да, бывают случаи, когда вы не можете не использовать, System.out.printlnи, Throwable.printStackTraceконечно же, требуется мнение разработчика. Я был немного обеспокоен тем, что пропущена часть о безопасности потоков. Если вы посмотрите на большинство реализаций регистраторов, вы заметите, что они синхронизируют часть, в которую записываются записи журнала (даже на консоль), хотя они не получают мониторы на System.errили System.out.
Vineet Reynolds
2
Могу ли я заметить, что ни одна из этих причин не применима к переопределениям printStackTrace, которые принимают объект PrintStream или PrintWriter в качестве параметра?
ESRogs
1
@Geek, последний - это дескриптор файла процесса со значением 2. Первый - это просто абстракция.
Vineet Reynolds
1
В исходном коде JDK printStackTrace () он использует synchronized для блокировки System.err PrintStream, поэтому это должен быть поточно-ориентированный метод.
EyouGo
33

Здесь вы затрагиваете несколько вопросов:

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

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

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

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

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

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

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

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

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

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

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


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

Томаш Нуркевич
источник
твой ответ - тот, который я получил.
Blasanka
«самые« исключительные »обстоятельства». отлично.
awwsmm
19

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

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

Сурадж Чандран
источник
11

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

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

пример

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

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.)

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

coobird
источник
1
Это. Довольно часто лучше просто не перехватывать исключение, по крайней мере, на уровне метода, чем перехватывать, печатать трассировку стека и продолжать работу, как будто проблем не возникло.
eis
10

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

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

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

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

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

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

NPE
источник
«исключение не может быть убрано» - вы имели в виду, что исключение распространяется вверх по стеку? чем ведение журнала отличается от printStackTrace ()?
MasterJoe
5

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

О Чин Бун
источник
Верно, хотя мы внимательно следим за выходом нашей производственной консоли.
Крис Найт,
Можете ли вы объяснить, что вы имеете в виду под «внимательно смотреть»? Как и кто? С какой периодичностью?
О Чин Бун
Внимательно наблюдая, я должен был сказать, когда потребуется. Это скользящий файл журнала, который является одним из нескольких портов вызова, если что-то пойдет не так с нашим приложением.
Крис Найт
3

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

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

Hajo Thelen
источник
Хорошая точка зрения. Злоупотребление printStackTrace () может взорвать наши файлы журналов или, по крайней мере, заполнить их кучей бесполезной информации,
Крис Найт,
@ChrisKnight - не могли бы вы предложить статью, в которой объясняется, как файлы журналов могут быть взорваны бесполезной информацией? Спасибо.
MasterJoe
3

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

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

AVEE
источник
0

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

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

gumkins
источник
0

Чтобы избежать запутанной проблемы с выходным потоком, упомянутой @Vineet Reynolds

вы можете распечатать его на стандартный вывод: e.printStackTrace(System.out);

Traeyee
источник