到目前为止,我一直避免使用 log.Fatal
,但我最近偶然发现了这些问题; code-coverage 和 tests-using-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)
}
}
...
}
如果最好的做法是避免使用 log.Fatal
,那么为什么在标准库中完全使用它,我本以为只会返回一个错误。调用 os.Exit
并且不为应用程序提供任何清理机会似乎对库的用户不公平。
我可能很天真,因此作为一种更好的做法,我的问题似乎是调用 log.Panic
可以恢复并且我理论上长期运行的稳定应用程序可能有机会从灰烬中重生。
那么 Go 的最佳实践是什么时候应该使用 log.Fatal 呢?
原文由 miltonb 发布,翻译遵循 CC BY-SA 4.0 许可协议
可能只有我一个人,但这是我使用
log.Fatal
的方式。根据 UNIX 约定,遇到错误的进程应尽早失败并返回非零退出代码。这引导我遵循以下使用指南log.Fatal
当……func init()
都发生了错误,因为这些错误分别发生在处理导入时或调用 main func 之前。相反,我只做不直接影响库或 cmd 应该做的工作单元的事情。例如,我设置日志记录并检查我们是否有一个健全的环境和参数。如果我们有无效的标志,就不需要运行 main 了,对吧?如果我们不能提供适当的反馈,我们应该尽早告知。cp
的实现,它开始是非交互式的并递归地复制一个目录。现在,假设我们在目标目录中遇到一个与要复制到那里的文件同名(但内容不同)的文件。由于我们不能要求用户决定要做什么,而且我们不能复制这个文件,所以我们遇到了问题。因为当我们以退出代码零结束时,用户会假设源目录和目标目录是精确的副本,所以我们不能简单地跳过有问题的文件。但是,我们不能简单地覆盖它,因为这可能会破坏信息。这是我们无法根据用户的明确请求恢复的情况,因此我将使用log.Fatal
来解释这种情况,在此遵守尽早失败的原则。