Почему exception.printStackTrace () считается плохой практикой? - PullRequest
117 голосов
/ 19 сентября 2011

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

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

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

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

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

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

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

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

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

Ответы [ 9 ]

114 голосов
/ 19 сентября 2011

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

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

32 голосов
/ 19 сентября 2011

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

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

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

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

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

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

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

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

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

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

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

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


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

17 голосов
/ 19 сентября 2011

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

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

11 голосов
/ 19 сентября 2011

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

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

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

9 голосов
/ 19 сентября 2011

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

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

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

4 голосов
/ 19 сентября 2011

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

3 голосов
/ 19 сентября 2011

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

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

3 голосов
/ 19 сентября 2011

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

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

0 голосов
/ 27 июля 2017

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

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

...