英文:
Why should I call os.Exit at most once in the main function?
问题
我开始了一份新工作,我们被要求使用Uber的Go编码规范。我对其中一项指南"Exit Once"不太确定:
如果可能的话,在
main()
函数中最多只调用一次os.Exit
或log.Fatal
。如果有多个导致程序终止的错误场景,将该逻辑放在一个单独的函数中,并从该函数返回错误。
这难道不意味着将main()
函数转移到另一个函数中(比如run()
)吗?这对我来说似乎有点多余。Uber的这种做法有什么好处呢?
英文:
I started a new job and we've been instructed to use Ubers Go coding standards. I'm not sure about one of their guidelines entitled "Exit Once":
> If possible, prefer to call os.Exit
or log.Fatal
at most once in your main()
. If there are multiple error scenarios that halt program execution, put that logic under a separate function and return errors from it.
Wouldn't this just mean offloading main()
into another function (run()
)? This seems a little superfluous to me. What benefits does Uber's approach have?
答案1
得分: 2
我不熟悉Uber的完整Go编码规范,但是那个建议是正确的。os.Exit
的一个问题是它会非常粗暴地结束程序,而不会执行任何待处理的延迟函数调用:
Exit会以给定的状态码退出当前程序。按照惯例,代码零表示成功,非零表示错误。
程序立即终止;延迟函数不会被运行。
(我强调的部分)
然而,这些延迟函数调用可能负责重要的清理任务。考虑Uber的示例代码片段:
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()
// 如果我们在这行之后调用log.Fatal,
// f.Close将不会被调用。
b, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}
// ...
}
如果ioutil.ReadAll
返回一个非nil的错误,将调用log.Fatal
;而且因为log.Fatal
在底层调用了os.Exit
,延迟调用f.Close
将不会被执行。在这种特定情况下,这并不是很严重,但是想象一下,如果延迟调用涉及一些清理工作,比如删除文件,那么你会让你的磁盘处于一个不干净的状态。关于这个主题的更多信息,请参见Go Time podcast的第112集,其中讨论了这些考虑因素。
因此,在程序中尽量避免使用os.Exit
、log.Fatal
等方法是一个好主意。Uber的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的博文为什么不应该在Golang中使用func main。
英文:
I'm not familiar with Uber's entire Go coding standards, but that particular piece of advice is sound. One issue with os.Exit
is that it puts an end to the programme very brutally, without honouring any deferred function calls pending:
> Exit causes the current program to exit with the given status code. Conventionally, code zero indicates success, non-zero an error.
> The program terminates immediately; deferred functions are not run.
(my emphasis)
However, those deferred function calls may be responsible for important cleanup tasks. Consider Uber's example code snippet:
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)
}
// ...
}
If ioutil.ReadAll
returns a non-nil error, log.Fatal
is called; and because log.Fatal
calls os.Exit
under the hood, the deferred call to f.Close
will not be run. In this particular case, it's not that serious, but imagine a situation where deferred calls involved some cleanup, like removing files; you'd leave your disk in an unclean state. For more on that topic, see episode #112 of the Go Time podcast, in which these considerations were discussed.
Therefore, it's a good idea to eschew os.Exit
, log.Fatal
, etc. "deep" in your programme. A run
function as described in Uber's Go coding standards allows deferred calls to be run as they should before programme execution ends (potentially with a non-zero status code):
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
}
// ...
}
An additional benefit of this approach is that, although the main
function itself isn't readily testable, you can design such a run
function with testability in mind; see Mat Ryer's blog post on that topic.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论