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

Chr*_*ght 120 java exception-handling printstacktrace

有一个很大材料这表明印刷异常的堆栈跟踪是不好的做法.

例如,来自Checkstyle的RegexpSingleline检查:

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

但是,我很难找到任何能够提供正确理由的地方,因为堆栈跟踪在跟踪导致异常的原因时非常有用.我所知道的事情:

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

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

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

  4. 打印堆栈跟踪不构成错误处理.它应该与其他信息记录和异常处理相结合.

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

Vin*_*lds 117

Throwable.printStackTrace()将堆栈跟踪写入System.errPrintStream.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目标文件/设备是否相同),这对于支持日志文件的健全性来说是相当沉重的代价.举一个例子,ConsoleHandlerStreamHandler类负责将日志记录附加到控制台,在提供的日志工具中java.util.logging; 发布日志记录的实际操作是同步的 - 每个尝试发布日志记录的线程也必须获取与该StreamHandler实例关联的监视器上的锁.如果您希望使用System.out/ 具有相同的非交错日志记录System.err保证,则必须确保相同 - 消息以可序列化的方式发布到这些流.

考虑到上述所有情况,以及Throwable.printStackTrace()实际上非常有用的非常有限的情况,通常会发现调用它是一种不好的做法.


扩展前面段落中的一个参数,Throwable.printStackTrace与写入控制台的记录器一起使用也是一个不好的选择.这部分是由于记录器将在不同的监视器上同步的原因,而您的应用程序(可能,如果您不希望交错的日志记录)在不同的监视器上同步.当您在应用程序中使用两个写入同一目标的不同记录器时,该参数也很有用.

  • 我是否错误地注意到这些原因都不适用于将PrintStream或PrintWriter对象作为参数的printStackTrace覆盖? (2认同)

Tom*_*icz 32

你在这里触及多个问题:

1)堆栈跟踪永远不应对最终用户可见(出于用户体验和安全目的)

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

  • 它们非常模糊且难以理解,应用程序看起来非常不友好.
  • 向最终用户显示堆栈跟踪可能会带来潜在的安全风险.纠正我,如果我错了,PHP实际上在堆栈跟踪中打印函数参数 - 很棒,但非常危险 - 如果你在连接数据库时遇到异常,你有什么可能在堆栈跟踪中?

2)生成堆栈跟踪是一个相对昂贵的过程(虽然在大多数"异常"情况下不太可能成为问题)

在创建/抛出异常时会产生堆栈跟踪(这就是为什么抛出异常需要付出代价),打印并不昂贵.实际上,您可以Throwable#fillInStackTrace()有效地覆盖自定义异常,从而使抛出异常几乎与简单的GOTO语句一样便宜.

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

很好的一点.这里的主要问题是:如果框架为您记录异常,则不执行任何操作(但请确保它执行!)如果您想自己记录异常,请使用LogbackLog4J之类的日志框架,而不是将它们放在原始控制台上因为它很难控制它.

使用日志记录框架,您可以轻松地将堆栈跟踪重定向到文件,控制台,甚至将它们发送到指定的电子邮件地址.硬编码printStackTrace()你必须忍受sysout.

4)打印堆栈跟踪不构成错误处理.它应该与其他信息记录和异常处理相结合.

再次:SQLException正确记录(使用完整的堆栈跟踪,使用日志记录框架)并显示良好:" 抱歉,我们目前无法处理您的请求 "消息.你真的认为用户对原因感兴趣吗?你看过StackOverflow错误屏幕了吗?它非常幽默,但没有透露任何细节.但是,它确保用户将调查问题.

但他立即给你打电话,你需要能够诊断出问题.因此,您需要两者:正确的异常日志记录和用户友好的消息.


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


Sur*_*ran 19

首先,printStackTrace()并不昂贵,因为当自己创建异常时,堆栈跟踪会被填充.

我们的想法是通过记录器框架传递任何记录日志,以便可以控制日志记录.因此,不要使用printStackTrace,只需使用类似的东西Logger.log(msg, exception);


coo*_*ird 11

打印异常的堆栈跟踪本身并不构成不好的做法,但只有在发生异常时才打印stace跟踪可能是这里的问题 - 通常,打印堆栈跟踪是不够的.

此外,如果catch块中执行的所有操作都是a,则可能会怀疑没有执行正确的异常处理e.printStackTrace.处理不当可能意味着问题被忽略,最坏的情况是在未定义或意外状态下继续执行的程序.

让我们考虑以下示例:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();
Run Code Online (Sandbox Code Playgroud)

在这里,我们想要继续进行一些需要进行初始化的处理之前进行一些初始化处理.

在上面的代码中,应该捕获并正确处理异常,以防止程序继续执行continueProcessingAssumingThatTheStateIsCorrect我们可以假设会导致问题的方法.

在许多情况下,e.printStackTrace()表明正在吞下某些异常并且允许进行处理,就好像每次都没有问题一样.

为什么这会成为问题?

可怜的异常处理变得更加普遍的最大原因之一可能是由于Eclipse之类的IDE将如何自动生成将执行e.printStackTrace异常处理的代码:

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

(以上是try-catchEclipse自动生成的处理InterruptedException抛出的实际内容Thread.sleep.)

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


NPE*_*NPE 9

我认为你的理由清单非常全面.

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

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }
Run Code Online (Sandbox Code Playgroud)

上述代码的问题在于处理完全printStackTrace调用组成:异常处理不正确,也不允许转义.

另一方面,作为规则,每当我的代码中出现意外异常时,我总是记录堆栈跟踪.多年来,这项政策为我节省了大量的调试时间.

最后,更轻松的说明,上帝的完美例外.


Oh *_*oon 5

printStackTrace()打印到控制台。在生产环境中,没有人会注意到这一点。Suraj 是正确的,应该将此信息传递给记录器。


AVe*_*Vee 5

这不是不好的做法,因为 PrintStackTrace() 有一些“错误”,但因为它是“代码异味”。大多数情况下,PrintStackTrace() 调用存在是因为有人未能正确处理异常。一旦您以适当的方式处理异常,您通常不再关心 StackTrace。

此外,在 stderr 上显示堆栈跟踪通常仅在调试时有用,而不是在生产中,因为 stderr 通常无处可去。记录它更有意义。但是仅仅用记录异常来替换 PrintStackTrace() 仍然会给你留下一个失败的应用程序,但像什么也没发生一样继续运行。