lot material there表示打印异常的堆栈跟踪是不好的做法。
E.g。来自Checkstyle的RegexpSingleline检查:
可以使用此检查来查找常见的不良做法,例如调用ex.printStacktrace()
然而,我正在努力寻找能够提供正确理由的地方,因为堆栈跟踪肯定非常有用,可以追踪导致异常的原因。我所知道的事情:
最终用户永远不应该看到堆栈跟踪(出于用户体验和安全目的)
生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“特殊”情况下不太可能成为问题)
许多日志记录框架会为您打印堆栈跟踪(我们的不会也不会,我们无法轻易更改)
打印堆栈跟踪不构成错误处理。它应该与其他信息记录和异常处理相结合。
还有哪些其他原因可以避免在代码中打印堆栈跟踪?
答案 0 :(得分:111)
Throwable.printStackTrace()
将堆栈跟踪写入System.err
PrintStream。
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
),这对于日志文件的健全性来说是相当沉重的代价。举个例子,ConsoleHandler
和StreamHandler
类负责在java.util.logging
提供的日志记录工具中将日志记录附加到控制台;发布日志记录的实际操作是同步的 - 每个尝试发布日志记录的线程也必须获取与StreamHandler
实例关联的监视器上的锁。如果您希望使用System.out
/ System.err
保证具有非交错日志记录,则必须确保相同 - 消息以可序列化方式发布到这些流。
考虑到上述所有情况,以及Throwable.printStackTrace()
实际上非常有用的非常有限的情况,通常会发现调用它是一种不好的做法。
在前面一段中扩展参数,将Throwable.printStackTrace
与写入控制台的记录器结合使用也是一个不好的选择。这部分是由于记录器将在不同的监视器上同步的原因,而您的应用程序(可能,如果您不希望交错的日志记录)在不同的监视器上同步。当您在应用程序中使用两个写入同一目标的不同记录器时,该参数也很有用。
答案 1 :(得分:32)
您在这里涉及多个问题:
1)堆栈跟踪永远不应对最终用户可见(出于用户体验和安全目的)
是的,应该可以访问它来诊断最终用户的问题,但最终用户不应该看到它们有两个原因:
2)生成堆栈跟踪是一个相对昂贵的过程(虽然在大多数“异常”情况下不太可能成为问题)
在创建/抛出异常时会生成堆栈跟踪(这就是为什么抛出异常需要付出代价),打印并不昂贵。实际上,您可以在自定义异常中覆盖Throwable#fillInStackTrace()
,从而使抛出异常几乎与简单的GOTO语句一样便宜。
3)许多日志记录框架将为您打印堆栈跟踪(我们的不会也不会,我们无法轻易更改它)
很好的一点。这里的主要问题是:如果框架为您记录异常,则不执行任何操作(但请确保它执行!)如果您想自己记录异常,请使用Logback或Log4J等日志框架,不要将它们放在原始控制台上,因为它很难控制它。
使用日志记录框架,您可以轻松地将堆栈跟踪重定向到文件,控制台,甚至将它们发送到指定的电子邮件地址。使用硬编码的printStackTrace()
,您必须使用sysout
。
4)打印堆栈跟踪不构成错误处理。它应该与其他信息记录和异常处理相结合。
再次:正确记录SQLException
(使用完整的堆栈跟踪,使用日志记录框架)并显示不错:“抱歉,我们目前无法处理您的请求”消息。你真的认为用户对原因感兴趣吗?你看过StackOverflow错误屏幕了吗?它非常幽默,但没有透露任何细节。但是,它可以确保用户调查问题。
但他会立即给你打电话,你需要能够诊断问题。因此,您需要两者:正确的异常日志记录和用户友好的消息。
总结:始终记录异常(最好使用logging framework),但不要将它们暴露给最终用户。仔细考虑GUI中的错误消息,仅在开发模式下显示堆栈跟踪。
答案 2 :(得分:17)
首先, printStackTrace()并不昂贵,因为在创建异常时会填充堆栈跟踪。
我们的想法是通过记录器框架传递任何记录日志,以便可以控制日志记录。因此,不要使用printStackTrace,只需使用类似Logger.log(msg, exception);
答案 3 :(得分:11)
打印异常的堆栈跟踪本身并不构成不好的做法,但是当发生异常时仅打印stace跟踪可能是这里的问题 - 通常只打印堆栈跟踪是还不够。
此外,如果在catch
块中执行的所有操作都是e.printStackTrace
,则可能会怀疑没有执行正确的异常处理。处理不当可能意味着问题被忽略,最坏的情况是程序继续在未定义或意外状态下执行。
示例强>
让我们考虑以下示例:
try {
initializeState();
} catch (TheSkyIsFallingEndOfTheWorldException e) {
e.printStackTrace();
}
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
来处理InterruptedException
引发的Thread.sleep
。)
对于大多数应用程序,只是将堆栈跟踪打印到标准错误可能不够。在许多情况下,不正确的异常处理可能导致应用程序在意外状态下运行,并可能导致意外和未定义的行为。
答案 4 :(得分:9)
我认为你的理由清单非常全面。
我不止一次遇到的一个特别糟糕的例子是:
try {
// do stuff
} catch (Exception e) {
e.printStackTrace(); // and swallow the exception
}
上述代码的问题在于,处理包含printStackTrace
调用的完全:异常处理不正确,也不允许转义。
另一方面,作为规则,每当我的代码中出现意外异常时,我总是记录堆栈跟踪。多年来,这项政策为我节省了大量的调试时间。
最后,更轻松一点,God's Perfect Exception。
答案 5 :(得分:4)
printStackTrace()
打印到控制台。在生产环境中,没有人会注意到这一点。 Suraj是正确的,应该将此信息传递给记录器。
答案 6 :(得分:3)
在服务器应用程序中,stacktrace会炸毁你的stdout / stderr文件。它可能会变得越来越大,并且充满了无用的数据,因为通常你没有上下文,没有时间戳等等。
e.g。使用tomcat作为容器时的catalina.out
答案 7 :(得分:3)
这是不错的做法,因为PrintStackTrace()的某些内容是“错误的”,但因为它的'代码味道'。 大多数时候PrintStackTrace()调用都在那里因为某人未能正确处理异常。一旦以正确的方式处理异常,您通常就不再关心StackTrace了。
此外,在stderr上显示堆栈跟踪通常仅在调试时有用,而不是在生产中,因为stderr通常无处可去。记录它更有意义。但是,仅仅通过记录异常来替换PrintStackTrace()仍然会使您失败但仍然保持运行的应用程序。
答案 8 :(得分:0)
正如有些人已经在这里提到的那样,如果你只是在e.printStackTrace()
区块中调用catch
,那么问题就是吞咽异常。它不会停止线程执行,并且会在try块之后继续正常状态。
而不是你需要尝试从异常中恢复(如果它是可恢复的),或者抛出RuntimeException
,或者将异常冒泡到调用者以避免静默崩溃(例如,由于记录器配置不正确。)
答案 9 :(得分:0)
为避免@Vineet Reynolds提到的输出流纠结问题
您可以将其打印到标准输出:
e.printStackTrace(System.out);