到目前为止我一直避免使用log.Fatal,但我最近偶然发现了这些问题; 代码覆盖和测试使用对数致命.
100个代码覆盖问题中的一条评论说:
...在绝大多数情况下,
log.Fatal应该只在main或init函数中使用(或者可能只是某些东西只能直接从它们调用)"
它让我思考,所以我开始查看Go提供的标准库代码.有很多例子,库中的测试代码使用log.Fatal它似乎很好.测试代码之外还有一些例子net/http,如下图所示:
// net/http/transport.go
func (t *Transport) putIdleConn(pconn *persistConn) bool {
...
for _, exist := range t.idleConn[key] {
if exist == pconn {
log.Fatalf("dup idle pconn %p in freelist", pconn)
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
如果最好的做法是避免使用log.Fatal,为什么在标准库中使用它,我本来希望只返回一个错误.对库的用户来说,os.Exit调用它并不提供应用程序清理的任何机会似乎是不公平的.
我可能是天真的,所以我的问题似乎是一个更好的实践似乎是呼唤log.Panic哪些可以恢复,我理论上长期稳定的应用可能有可能从灰烬中升起.
那么什么是最佳实践说Go应该什么时候应该使用log.Fatal?
Mar*_*erg 27
它可能只是我,但这是我如何使用log.Fatal.根据UNIX约定,遇到错误的进程应尽早使用非零退出代码失败.这导致我在以下指导时使用log.Fatal......
func init(),因为这些错误分别在处理导入或调用主函数之前发生.相反,我只做不直接影响库或cmd应该做的工作单元的东西.例如,我设置了日志记录并检查我们是否有一个理智的环境和参数.如果我们有无效标志,则无需运行main,对吧?如果我们无法提供适当的反馈,我们应该尽早告诉我们.cp,它开始是非交互式的,并且递归地复制一个目录.现在,让我们假设我们在目标目录中遇到一个文件,该文件具有相同的名称(但内容不同)作为要复制的文件.由于我们不能要求用户决定做什么而我们无法复制此文件,因此我们遇到了问题.因为当我们完成退出代码零时,用户将假定源目录和目标目录是精确副本,我们不能简单地跳过相关文件.但是,我们不能简单地覆盖它,因为这可能会破坏信息.这是我们无法从用户的明确请求中恢复log.Fatal的情况,因此我将用于解释这种情况,从而尽快遵守原则.小智 10
马库斯,我看到了你的回应,我认为它非常好而且非常有洞察力,我倾向于同意你的分析。很难一概而论,尽管作为 Go 的新手,我一直在思考这一点。我认为在理论层面上,如果我们正在寻找计算方面的最佳实践,无论操作系统、包框架或库如何,记录器的职责就是简单地记录。在任何级别,记录员的职责:
如果程序运行正常,日志记录包或任何包都没有也不应该有权使程序崩溃。任何中间件或库都应该遵循抛出/捕获模式,以便调用者有机会捕获所有抛出的异常。这也是在应用程序中遵循的一个很好的模式,当您构建为应用程序的各个部分以及可能的其他应用程序提供支持的基础和包时,它们永远不应该直接使应用程序崩溃。相反,它们应该抛出致命异常,允许程序处理。我认为这也解决了马库斯你的一些观点,因为它可以在未捕获时立即提醒呼叫者,作为致命的崩溃。
在大多数情况下,我可以直接利用 Go 中的 log.Fatal 来实现直接面向用户的 cli 工具,我认为这才是它真正的目的所在。我认为作为处理跨包致命错误的长期方法没有很好的意义。