为什么是exception.printStackTrace()被认为是不好的做法?

那里有很多的材料 ,这表明打印exception的堆栈跟踪是不好的做法。

例如从Checkstyle的RegexpSingleline检查:

这个检查可以用来查找常见的不良做法,如调用ex.printStacktrace()

然而,我正努力find任何地方给出了一个有效的原因,因为肯定堆栈跟踪是非常有用的在追查什么导致exception。 我知道的事情是:

  1. 最终用户永远不会看到堆栈跟踪(用于用户体验和安全目的)

  2. 生成堆栈跟踪是一个相对昂贵的过程(尽pipe在大多数“特殊”情况下不太可能成为问题)

  3. 许多日志logging框架将为您打印堆栈跟踪(我们不会,也不会轻易更改)

  4. 打印堆栈跟踪不构成error handling。 它应该与其他信息logging和exception处理相结合。

还有什么其他原因可以避免在代码中打印堆栈跟踪?

Throwable.printStackTrace()将堆栈跟踪写入System.err PrintStream。 System.errstream和JVM进程的底层标准“错误”输出stream可以被redirect

  • 调用System.setErr()来更改System.err指向的目标。
  • 或通过redirect进程的错误输出stream。 错误输出stream可以被redirect到文件/设备
    • 其内容可能被人员忽视,
    • 文件/设备可能无法进行日志轮转,推断在归档文件/设备的现有内容之前需要重新启动进程才能closures打开的文件/设备句柄。
    • 或者文件/设备实际上丢弃了写入它的所有数据,就像/dev/null

从上面推论,调用Throwable.printStackTrace()构成有效(不好/很好)exception处理行为

  • 如果在应用程序的整个生命周期中没有重新分配System.err
  • 如果您在应用程序运行时不需要轮转,
  • 如果应用程序的接受/devise日志实践是写入System.err (和JVM的标准错误输出stream)。

在大多数情况下,上述条件不能满足。 有人可能不知道在JVM中运行的其他代码,而且无法预测日志文件的大小或进程的运行时间,而devise良好的日志logging将围绕编写“机器可分析”日志文件(a在一个已知的目的地,更好的但可选的function),以帮助支持。

最后,应该记住, Throwable.printStackTrace()的输出肯定会与写入到System.err其他内容(如果两者都被redirect到相同的文件/设备,甚至可能是System.out Throwable.printStackTrace()交错。 这是一个烦人的事情(对于单线程应用程序),必须处理,因为在这种情况下,围绕exception的数据是不容易parsing的。 更糟糕的是,multithreading应用程序很可能会产生令人困惑的日志,因为Throwable.printStackTrace() 不是线程安全的

当多个线程同时调用Throwable.printStackTrace()时,没有同步机制可以将堆栈跟踪的写入同步到System.err 。 解决这个问题实际上需要你的代码在与System.err相关的监视器上进行同步(如果目标文件/设备是相同的,那么还需要System.out ),这对于日志文件的完整性来说是相当昂贵的代价。 举个例子, ConsoleHandlerStreamHandler类负责将日志logging附加到控制台,这些日志logging由java.util.logging提供; 发布日志logging的实际操作是同步的 – 每个尝试发布日志logging的线程都必须获取与StreamHandler实例关联的监视器上的locking。 如果您希望使用System.out / System.err保证具有非交错日志logging,则必须确保相同 – 消息以可序列化的方式发布到这些stream。

考虑到上面的所有内容,以及Throwable.printStackTrace()实际上非常有用的非常有限的场景,通常情况下调用它是一个不好的习惯。


在前面的段落中扩展参数,将Throwable.printStackTrace与写入控制台的logging器结合使用也是一个糟糕的select。 这部分是由于logging器会在不同的监视器上进行同步的原因,而您的应用程序(如果您不想交错的日志logging,可能会在不同的监视器上同步)。 当您在应用程序中使用写入相同目的地的两个不同的logging器时,参数也保持不变。

您在这里触及多个问题:

1)堆栈跟踪对于最终用户来说绝对不可见(为了用户体验和安全目的)

是的,它应该可以用来诊断最终用户的问题,但最终用户不应该看到它们,原因有两个:

  • 他们是非常模糊和不可读的,应用程序将看起来非常不友好的用户。
  • 向最终用户显示堆栈跟踪可能会带来潜在的安全风险。 纠正我,如果我错了,PHP实际上打印堆栈跟踪函数参数 – 辉煌,但非常危险 – 如果你连接到数据库时,你会得到例外,你可能在堆栈跟踪?

2)生成堆栈跟踪是一个相对昂贵的过程(尽pipe在大多数“例外”情况下不太可能成为问题)

生成堆栈跟踪发生在创build/抛出exception(这就是为什么抛出一个exception带有价格),打印并不昂贵。 事实上,您可以在自定义exception中覆盖Throwable#fillInStackTrace() ,使得抛出exception几乎与简单的GOTO语句一样便宜。

3)许多日志框架将打印你的堆栈跟踪(我们不,不,我们不能轻易改变它)

非常好的一点。 这里的主要问题是:如果框架为您loggingexception,则不要执行任何操作(但要确保它是这样做的)。如果您想自己loggingexception,请使用Logback或Log4J等日志框架,不要将它们放在原始控制台因为很难控制它。

使用日志框架,您可以轻松地将堆栈跟踪redirect到文件,控制台或甚至将它们发送到指定的电子邮件地址。 与硬编码printStackTrace()你必须与系统生活。

4)打印堆栈跟踪不构成error handling。 它应该与其他信息logging和exception处理相结合。

再次:正确loggingSQLException (使用完整的堆栈跟踪,使用日志框架),并显示很好:“ 对不起,我们目前无法处理您的请求 ”消息。 你真的认为用户对这个原因感兴趣吗? 你见过StackOverflow错误屏幕? 这很幽默,但没有透露任何细节。 但是,它确保用户问题将被调查。

但他立即打电话给你,你需要能够诊断问题。 所以你需要:正确的exceptionlogging和用户友好的消息。


总结: 总是loggingexception(最好使用日志框架 ),但不要将它们暴露给最终用户。 仔细考虑一下GUI中的错误消息,仅在开发模式下显示堆栈跟踪。

首先, printStackTrace()并不昂贵,因为在创buildexception时,堆栈跟踪会被填充。

这个想法是通过一个logging器框架来传递所有日志,以便日志可以被控制。 因此,而不是使用printStackTrace,只需使用像Logger.log(msg, exception);

打印exception的堆栈跟踪本身并不是一个坏习惯,但是只有在发生exception时才打印stace trace可能是这里的问题 – 通常情况下,仅打印堆栈跟踪是不够的。

另外,如果在catch块中执行的所有操作都是e.printStackTrace ,就有可能怀疑没有执行正确的exception处理。 处理不当可能意味着最多只能忽略一个问题,而最坏的情况是一个程序继续以一个不确定的或意想不到的状态执行。

我们来看下面的例子:

 try { initializeState(); } catch (TheSkyIsFallingEndOfTheWorldException e) { e.printStackTrace(); } continueProcessingAssumingThatTheStateIsCorrect(); 

在这里,我们想要进行一些初始化处理,然后继续进行一些需要进行初始化的处理。

在上面的代码中,exception应该已经被捕获并且被正确地处理,以防止程序continueProcessingAssumingThatTheStateIsCorrect进行到正在处理的正在处理的状态正确的方法,这可能会导致问题。

在很多情况下, e.printStackTrace()表示正在吞咽一些exception,并且允许处理进行,就像没有发生任何问题一样。

为什么这成为一个问题?

糟糕的exception处理变得越来越stream行的最大原因可能之一是由于IDE等IDE如何自动生成将执行exception处理的e.printStackTrace代码:

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

(以上是由Eclipse自动生成的用于处理由Thread.sleep引发的InterruptedException的实际try-catch 。)

对于大多数应用程序,只是将堆栈跟踪打印到标准错误可能不够。 不正确的exception处理在很多情况下可能会导致应用程序以意外状态运行,并可能导致意外和未定义的行为。

我认为你列出的理由是相当全面的。

我遇到不止一次的一个特别糟糕的例子是这样的:

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

上述代码的问题是处理完全printStackTrace调用组成:exception处理不当,也不允许转义。

另一方面,通常我总是logging堆栈跟踪,只要有代码中出现意外的exception。 多年来这个策略为我节省了很多debugging时间。

最后,在一个更轻的笔记, 上帝的完美例外 。

printStackTrace()打印到控制台。 在生产环境中,没有人会注意到这一点。 苏拉吉是正确的,应该把这个信息传递给logging者。

在服务器应用程序中,stacktrace会抛出你的stdout / stderr文件。 它可能变得越来越大,充满无用的数据,因为通常你没有上下文和时间戳等等。

例如使用tomcat作为容器时的catalina.out

这不是坏习惯,因为PrintStackTrace()有些错误,而是因为它是'代码味道'。 大多数情况下PrintStackTrace()调用是因为有人未能正确处理exception。 一旦你以适当的方式处理exception,你一般不再关心StackTrace。

另外,在stderr上显示堆栈跟踪通常只在debugging时才有用,而不是在生产中,因为stderr往往无处可用。 logging它更有意义。 但是,只要将PrintStackTrace()replace为loggingexception,仍然会给你一个失败的应用程序,但是它保持运行,就像什么都没有发生过。

正如一些人已经在这里提到的问题是,如果你只是在catch块中调用e.printStackTrace() ,吞下e.printStackTrace() 。 它将不会停止线程执行,并会像正常情况一样在try块之后继续执行。

而不是你需要尝试从exception中恢复(万一它是可恢复的),或者抛出RuntimeException ,或者为了避免沉默的崩溃(例如,由于不正确的logging器configuration)而将exception冒泡给调用者。