到目前为止,我避免使用log.Fatal
,但最近我偶然发现了这些问题; code-coverage和tests-using-log-fatal。
来自100个代码覆盖率问题的评论之一说:
...在大多数情况下,
log.Fatal
仅应在main函数或init函数中使用(或可能只应直接从它们中调用某些东西)“
我开始思考,所以我开始研究Go随附的标准库代码。有很多示例,其中库中的test代码使用了log.Fatal
,这似乎很好。测试代码之外还有一些示例,例如net/http
,如下所示:
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
,并且我的理论上长期运行的稳定应用程序可能有机会从灰烬中恢复过来。
那么,最佳实践对Go应该在什么时候使用log.Fatal表示什么?
可能只是我,但这是我使用log.Panic
的方式。根据UNIX约定,遇到错误的进程应使用非零退出代码尽早失败。这导致我在以下情况下使用以下准则来使用log.Fatal
…
log.Fatal
中的任何一个都发生错误,因为这些错误分别在处理导入或调用主函数之前发生。相反,我只做不会直接影响库或cmd应该做的工作单元的事情。例如,我设置了日志记录并检查我们是否拥有合理的环境和参数。如果我们的标志无效,则无需运行main,对吗?如果我们不能提供适当的反馈,我们应该尽早告知。func init()
的实现,并且它开始是非交互式的,并且递归地复制目录。现在,假设我们在目标目录中遇到一个文件,该文件与要复制到该目录的文件具有相同的名称(但内容不同)。由于我们无法要求用户决定要做什么,并且我们不能复制此文件,因此我们遇到了问题。由于在退出代码为零时,用户将假定源目录和目标目录是完全相同的副本,因此我们不能简单地跳过相关文件。但是,我们不能简单地覆盖它,因为这可能会破坏信息。这种情况我们无法从用户的每个显式请求中恢复,因此我将使用cp
来解释这种情况,从而遵循尽早失败的原则。[马库斯,我遇到了您的答复,我认为这是非常出色且很有见地的,我倾向于同意您的意见。很难一概而论,尽管我一直在考虑这一点,并且作为Go的新手。我认为从理论上讲,如果我们寻求计算的最佳实践,而不管操作系统,软件包框架或库如何,记录器的责任就是简单地记录日志。在任何级别,记录器的职责:
日志记录程序包或任何程序包没有,并且不应该有权使程序崩溃,如果它运行正常。任何中间件或库都应遵循throw / catch模式,并有机会使调用者捕获all异常。这也是在应用程序中遵循的一个很好的模式,因为您建立了为应用程序的各个部分以及潜在的其他应用程序提供动力的基础和程序包,因此它们绝不应直接使应用程序崩溃。相反,它们应该引发致命异常,从而允许程序进行处理。我认为这也可以解决您Marcus的一些要点,因为它可以在未被捕获时立即提醒呼叫者,这是致命的崩溃。
在大多数情况下,我可以享受到利用日志的乐趣。Fatalin Go直接用于直接面向用户的cli工具,我认为这是它真正旨在实现的简单性。我认为这是一种长期解决跨包致命错误的方法,没有任何意义。