7.2. 错误只处理一次
最后,我想提一下你应该只处理错误一次。 处理错误意味着检查错误值并做出单一决定。
// WriteAll writes the contents of buf to the supplied writer.
func WriteAll(w io.Writer, buf []byte) {
w.Write(buf)
}
如果你做出的决定少于一个,则忽略该错误。 正如我们在这里看到的那样, w.WriteAll
的错误被丢弃。
但是,针对单个错误做出多个决策也是有问题的。 以下是我经常遇到的代码。
func WriteAll(w io.Writer, buf []byte) error {
_, err := w.Write(buf)
if err != nil {
log.Println("unable to write:", err) // annotated error goes to log file
return err // unannotated error returned to caller
}
return nil
}
在此示例中,如果在 w.Write
期间发生错误,则会写入日志文件,注明错误发生的文件与行数,并且错误也会返回给调用者,调用者可能会记录该错误并将其返回到上一级,一直回到程序的顶部。
调用者可能正在做同样的事情
func WriteConfig(w io.Writer, conf *Config) error {
buf, err := json.Marshal(conf)
if err != nil {
log.Printf("could not marshal config: %v", err)
return err
}
if err := WriteAll(w, buf); err != nil {
log.Println("could not write config: %v", err)
return err
}
return nil
}
因此你在日志文件中得到一堆重复的内容,
unable to write: io.EOF
could not write config: io.EOF
但在程序的顶部,虽然得到了原始错误,但没有相关内容。
err := WriteConfig(f, &conf)
fmt.Println(err) // io.EOF
我想深入研究这一点,因为作为个人偏好, 我并没有看到 logging
和返回的问题。
func WriteConfig(w io.Writer, conf *Config) error {
buf, err := json.Marshal(conf)
if err != nil {
log.Printf("could not marshal config: %v", err)
// oops, forgot to return
}
if err := WriteAll(w, buf); err != nil {
log.Println("could not write config: %v", err)
return err
}
return nil
}
很多问题是程序员忘记从错误中返回。正如我们之前谈到的那样,Go 语言风格是使用 guard clauses
以及检查前提条件作为函数进展并提前返回。
在这个例子中,作者检查了错误,记录了它,但忘了返回。这就引起了一个微妙的错误。
Go 语言中的错误处理规定,如果出现错误,你不能对其他返回值的内容做出任何假设。由于 JSON
解析失败,buf
的内容未知,可能它什么都没有,但更糟的是它可能包含解析的 JSON
片段部分。
由于程序员在检查并记录错误后忘记返回,因此损坏的缓冲区将传递给 WriteAll
,这可能会成功,因此配置文件将被错误地写入。但是,该函数会正常返回,并且发生问题的唯一日志行是有关 JSON
解析错误,而与写入配置失败有关。