繁体   English   中英

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

[英]Why is exception.printStackTrace() considered bad practice?

有很多的材料,这表明印刷异常的堆栈跟踪是不好的做法。

例如,来自 Checkstyle 中的 RegexpSingleline 检查:

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

但是,我正在努力寻找任何可以提供正当理由的地方,因为堆栈跟踪对于跟踪导致异常的原因肯定非常有用。 我所知道的事情:

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

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

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

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

避免在代码中打印堆栈跟踪的其他原因是什么?

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()不是线程安全的

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

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


扩展前面一段中的论点,将Throwable.printStackTrace与写入控制台的记录器结合使用也是一个糟糕的选择。 这部分是由于记录器会在不同的监视器上同步的原因,而您的应用程序会(可能,如果您不想要交错的日志记录)在不同的监视器上同步。 当您在应用程序中使用两个不同的记录器写入同一目的地时,该论点也适用。

您在这里涉及多个问题:

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

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

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

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

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

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

很好的观点。 这里的主要问题是:如果框架为你记录异常,什么都不做(但要确保它记录了!)如果你想自己记录异常,使用LogbackLog4J 之类的日志框架,不要把它们放在原始控制台上因为它很难控制。

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

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

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

但是他立即给您打电话,您需要能够诊断问题。 所以你需要两者:适当的异常记录和用户友好的消息。


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

第一件事printStackTrace()正如您所说的那样并不昂贵,因为在创建异常本身时会填充堆栈跟踪。

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

打印异常的堆栈跟踪本身并不构成不良做法,但在发生异常时打印 stace 跟踪可能是这里的问题——通常,仅打印堆栈跟踪是不够的。

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

例子

让我们考虑以下示例:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

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

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

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

为什么这成为一个问题?

糟糕的异常处理变得更加普遍的最大原因之一可能是 Eclipse 等 IDE 如何自动生成代码,这些代码将为异常处理执行e.printStackTrace

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

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

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

我认为您列出的原因非常全面。

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

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

上面代码的问题在于处理完全printStackTrace调用组成:异常没有真正得到正确处理,也不允许逃逸。

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

最后,稍微说一下, 上帝的完美例外

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

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

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

在服务器应用程序中,堆栈跟踪会破坏您的 stdout/stderr 文件。 它可能会变得越来越大,并且充满了无用的数据,因为通常你没有上下文,没有时间戳等等。

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

正如一些人在这里已经提到的那样,问题在于吞咽异常,以防您只在catch块中调用e.printStackTrace() 它不会停止线程执行,并且会像正常情况一样在 try 块之后继续执行。

相反,您需要尝试从异常中恢复(如果它是可恢复的),或者抛出RuntimeException ,或者将异常冒泡给调用者以避免静默崩溃(例如,由于不正确的记录器配置)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM