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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Давайте будем гением компьютера.