提问人:Chris Knight 提问时间:9/19/2011 最后编辑:hallo545401Chris Knight 更新时间:9/19/2020 访问量:152331
为什么 exception.printStackTrace() 被认为是不良做法?
Why is exception.printStackTrace() considered bad practice?
问:
有很多材料表明,打印异常的堆栈跟踪是一种不好的做法。
例如,从 Checkstyle 中的 RegexpSingleline 检查:
此检查可用于查找常见的不良做法,例如调用 ex.printStacktrace()
但是,我正在努力找到任何给出正当理由的地方,因为堆栈跟踪肯定在跟踪导致异常的原因方面非常有用。我所知道的事情:
堆栈跟踪不应对最终用户可见(出于用户体验和安全目的)
生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“特殊”情况下不太可能成为问题)
许多日志记录框架会为您打印堆栈跟踪(我们的不会,不,我们不能轻易更改它)
打印堆栈跟踪不构成错误处理。它应该与其他信息日志记录和异常处理相结合。
还有哪些其他原因可以避免在代码中打印堆栈跟踪?
答:
首先,printStackTrace() 并不昂贵,因为堆栈跟踪是在创建异常本身时填充的。
这个想法是通过记录器框架传递任何进入日志的内容,以便可以控制日志记录。因此,不要使用 printStackTrace,只需使用类似Logger.log(msg, exception);
在服务器应用程序中,堆栈跟踪会破坏 stdout/stderr 文件。它可能会变得越来越大,并且充满了无用的数据,因为通常您没有上下文,也没有时间戳等等。
例如,使用 Tomcat 作为容器时的 catalina.out
评论
printStackTrace()
打印到控制台。在生产环境中,没有人会关注这一点。Suraj 是正确的,应该将此信息传递给记录器。
评论
您在这里遇到了多个问题:
1) 堆栈跟踪绝不应对最终用户可见(出于用户体验和安全目的)
是的,它应该可以用于诊断最终用户的问题,但最终用户不应看到它们,原因有两个:
- 它们非常晦涩难懂且不可读,应用程序看起来非常用户不友好。
- 向最终用户显示堆栈跟踪可能会带来潜在的安全风险。如果我错了,请纠正我,PHP 实际上在堆栈跟踪中打印函数参数 - 很棒,但非常危险 - 如果您在连接到数据库时遇到异常,您可能会在堆栈跟踪中做什么?
2) 生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“特殊”情况下不太可能成为问题)
在创建/抛出异常时生成堆栈跟踪(这就是为什么抛出异常是有代价的),打印成本并不高。事实上,您可以有效地覆盖自定义异常,使抛出异常几乎与简单的 GOTO 语句一样便宜。Throwable#fillInStackTrace()
3)许多日志框架会为你打印堆栈跟踪(我们的没有,不,我们不能轻易更改它)
非常好的观点。这里的主要问题是:如果框架为您记录了异常,则什么都不做(但要确保它这样做!如果您想自己记录异常,请使用 Logback 或 Log4J 等日志记录框架,不要将它们放在原始控制台上,因为很难控制它。
使用日志记录框架,您可以轻松地将堆栈跟踪重定向到文件、控制台,甚至将它们发送到指定的电子邮件地址。使用硬编码,您必须忍受 .printStackTrace()
sysout
4) 打印堆栈跟踪不构成错误处理。它应该与其他信息日志记录和异常处理相结合。
再次:正确记录(使用日志记录框架使用完整堆栈跟踪)并显示 nice:“抱歉,我们目前无法处理您的请求”消息。你真的认为用户对原因感兴趣吗?你见过StackOverflow错误屏幕吗?它非常幽默,但没有透露任何细节。但是,它确保用户将对问题进行调查。SQLException
但是他会立即打电话给你,你需要能够诊断问题。因此,您需要两者兼而有之:适当的异常日志记录和用户友好的消息。
总而言之:始终记录异常(最好使用日志记录框架),但不要将它们暴露给最终用户。仔细考虑 GUI 中的错误消息,仅在开发模式下显示堆栈跟踪。
评论
打印异常的堆栈跟踪本身并不构成不良做法,但仅在发生异常时打印 stace 跟踪可能是这里的问题 - 通常,仅打印堆栈跟踪是不够的。
此外,如果一个块中执行的所有操作都是 .处理不当充其量可能意味着一个问题被忽略,最坏的情况是程序在未定义或意外状态下继续执行。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();
}
(以上是 Eclipse 实际自动生成的,用于处理 抛出的 。try-catch
InterruptedException
Thread.sleep
对于大多数应用程序,仅将堆栈跟踪打印为标准错误可能还不够。在许多情况下,不正确的异常处理可能会导致应用程序在意外状态下运行,并可能导致意外和未定义的行为。
评论
我认为你列出的原因非常全面。
我不止一次遇到过一个特别糟糕的例子是这样的:
try {
// do stuff
} catch (Exception e) {
e.printStackTrace(); // and swallow the exception
}
上面代码的问题在于处理完全由调用组成:异常没有得到正确处理,也不允许它逃逸。printStackTrace
另一方面,通常,每当代码中出现意外异常时,我总是记录堆栈跟踪。多年来,此策略为我节省了大量调试时间。
最后,在更轻松的音符上,上帝的完美例外。
评论
这不是坏做法,因为 PrintStackTrace() 有些“错误”,而是因为它是“代码味”。 大多数情况下,PrintStackTrace() 调用之所以存在,是因为有人未能正确处理异常。一旦你以适当的方式处理了异常,你通常就不再关心 StackTrace 了。
此外,在 stderr 上显示堆栈跟踪通常仅在调试时有用,而不是在生产中,因为 stderr 通常无处可去。记录它更有意义。但是,仅将 PrintStackTrace() 替换为记录异常仍然会让您看到一个失败但像什么都没发生一样继续运行的应用程序。
Throwable.printStackTrace()
将堆栈跟踪写入 PrintStream。JVM 进程的流和底层标准“错误”输出流可以通过以下方式重定向System.err
System.err
- 调用
System.setErr(),
这将更改 指向的目标。System.err
- 或重定向进程的错误输出流。错误输出流可能会重定向到文件/设备
- 其内容可能被人员忽略,
- 文件/设备可能无法进行日志轮换,从而推断在存档文件/设备的现有内容之前,需要重新启动进程才能关闭打开的文件/设备句柄。
- 或者文件/设备实际上会丢弃写入它的所有数据,就像 一样。
/dev/null
从上面推断,调用构成有效(不好/很好)的异常处理行为,只有Throwable.printStackTrace()
- 如果您在应用程序的整个生命周期内没有被重新分配,
System.err
- 如果在应用程序运行时不需要日志轮换,
- 如果接受/设计了应用程序的日志记录实践,则写入(以及 JVM 的标准错误输出流)。
System.err
在大多数情况下,上述条件都不满足。人们可能不知道 JVM 中正在运行的其他代码,也无法预测日志文件的大小或进程的运行时持续时间,而精心设计的日志记录实践将围绕在已知目标中写入“机器可解析”日志文件(记录器中可取但可选的功能)来帮助提供支持。
最后,应该记住,的输出肯定会与写入的其他内容交错(甚至可能两者都重定向到同一个文件/设备)。这是一个必须处理的烦恼(对于单线程应用程序),因为在这样的事件中,围绕异常的数据不容易解析。更糟糕的是,多线程应用程序很可能会生成非常令人困惑的日志,因为这不是线程安全的。Throwable.printStackTrace()
System.err
System.out
Throwable.printStackTrace()
没有同步机制可以将堆栈跟踪的写入同步到多个线程同时调用时。解决此问题实际上需要您的代码在与关联的监视器上同步(如果目标文件/设备相同,并且还同步),这是为日志文件健全性付出的沉重代价。举个例子,and 类负责将日志记录追加到控制台,在 ;发布日志记录的实际操作是同步的 - 尝试发布日志记录的每个线程还必须获取与实例关联的监视器上的锁。如果您希望获得使用 / 的非交错日志记录的相同保证,则必须确保相同 - 消息以可序列化的方式发布到这些流。System.err
Throwable.printStackTrace()
System.err
System.out
ConsoleHandler
StreamHandler
java.util.logging
StreamHandler
System.out
System.err
考虑到上述所有情况,以及实际有用的非常有限的场景,通常事实证明,调用它是一种不好的做法。Throwable.printStackTrace()
扩展前面一段中的参数,与写入控制台的记录器结合使用也是一个糟糕的选择。这在一定程度上是由于记录器将在不同的监视器上同步,而您的应用程序将(可能,如果您不想要交错的日志记录)在不同的监视器上同步。当您在应用程序中使用两个不同的记录器写入同一目标时,该参数也适用。Throwable.printStackTrace
评论
System.out.println
Throwable.printStackTrace
System.err
System.out
正如一些人在这里已经提到的那样,问题在于吞咽除外,以防万一您只是调用块。它不会停止线程执行,并且会在 try 块之后继续,就像在正常情况下一样。e.printStackTrace()
catch
取而代之的是,您需要尝试从异常中恢复(如果它是可恢复的),或者抛出 ,或者将异常冒泡给调用者以避免静默崩溃(例如,由于记录器配置不正确)。RuntimeException
评论
.printStackTrace()
e.printStackTrace(System.out);