我开始了一份新工作,我们被要求使用Ubers Go编码标准。我不确定他们的一条题为";退出一次":
如果可能,请在
main()
中最多调用os.Exit
或log.Fatal
一次。如果存在多个暂停程序执行的错误场景,请将该逻辑置于一个单独的函数下,并从中返回错误
这难道不意味着将main()
卸载到另一个函数(run()
(中吗?这在我看来有点多余。优步的做法有什么好处?
我不熟悉优步的整个Go编码标准,但这条特别的建议是合理的。os.Exit
的一个问题是,它非常残酷地结束了程序,而没有遵守任何延迟的函数调用:
Exit会导致当前程序退出,并带有给定的状态代码。按照惯例,代码零表示成功,非零表示错误。程序立即终止延迟函数不运行。
(我的重点(
但是,那些延迟的函数调用可能负责重要的清理任务。考虑优步的示例代码片段:
package main
func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatal("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// If we call log.Fatal after this line,
// f.Close will not be called.
b, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}
// ...
}
如果ioutil.ReadAll
返回非零错误,则调用log.Fatal
;并且由于log.Fatal
在后台调用os.Exit
,所以对f.Close
的延迟调用将不会运行。在这种特殊的情况下,情况并没有那么严重,但想象一下,延迟调用涉及一些清理,比如删除文件;你会让你的磁盘处于不干净的状态。有关该主题的更多信息,请参阅Go Time播客第112集,其中讨论了这些考虑因素。
因此,避开os.Exit
、log.Fatal
等是个好主意;深";在你的节目中。优步Go编码标准中描述的run
函数允许在程序执行结束前按原样运行延迟调用(可能具有非零状态代码(:
package main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
// ...
}
这种方法的另一个好处是,尽管main
函数本身不容易测试,但您可以在设计这样的run
函数时考虑到可测试性;请参阅Mat Ryer关于该主题的博客文章。