简单的错误处理是使用 Fprintf 和 %v 在标准错误流上输出一条消息,%v 可使用默认格式显示任意类型的值。
为了保持示例代码简短,有时会对错误处理有意进行必定程度的忽略。明显的错误仍是要处理的。可是有些出现几率很小的错误,就忽略了,不过要标记所跳过的错误检查,就是加上注释。 html
根据情形,将有许多可能的处理场景,接下来是5个例子。 安全
最多见的情形是将错误传递下去,使得在子例程中发生的错误变为主调例程的错误。
一种是不作任何操做当即向调用者返回错误:服务器
resp, err := http.Get(url) if err != nil { return nil, err }
还有一种,不会直接返回,由于错误信息中缺失一些关键信息:ide
doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v\n", url, err) }
这里格式化了一条错误消息而且返回一个新的错误值。能够为原始的错误消息不断地添加上下文信息来创建一个可读的错误描述。当错误最终被程序的 main 函数处理时,它应该可以提供一个从最根本问题到整体故障的清晰因果链、这里有一个 NASA 的事故调查的例子:函数
genesis: crashed: no parachute: G-switch failed: bad relay orientation
由于错误频繁地串联起来,因此消息字符串首字母不该该大写并且应该避免换行。错误结果可能会很长,但能可以使用 grep 这样的工具找到须要的信息。 工具
须要添加的关键信息
有时候能够不用添加信息直接返回,有时候须要添加一些关键信息,由于错误信息里没有。好比 os.Open 打开文件时,返回的错误不只仅包括错误的信息,还包含文件的名字,所以调用者构造错误消息的时候不须要包含文件的名字这类信息。具体哪些信息是缺乏的关键信息须要在原始的错误消息的基础上添加?
通常地,f(x) 调用只负责报告函数的行为 f 和参数值 x,由于它们和错误的上下文相关。调用者则负责添加进一步的信息,可是 f(x) 自己并不会,而且在函数内部也没有这些信息。
好比上面的 html.Parse 返回的错误信息里不可能有 url 的信息,可是,是关键信息须要添加。而 os.Open 中,文件名字也是关键信息,可是这个正是函数的参数值,因此函数自己会返回这个信息,不须要另外添加。 ui
对于不固定或者不可预测的错误,在短暂的间隔后对操做进行重试是合乎情理的。超出必定的重试次数和限定的时间后再报错退出。
下面给出了完整的代码,暂时只看 WaitForServer 函数:url
package main import ( "fmt" "log" "net/http" "os" "time" ) // 尝试链接 url 对应的服务器 // 在一分钟内使用指数退避策略进行重试 // 全部的尝试失败后返回错误 func WaitForServer(url string) error { const timeout = 1 * time.Minute deadline := time.Now().Add(timeout) for tries := 0; time.Now().Before(deadline); tries++ { _, err := http.Head(url) if err == nil { return nil // 成功 } log.Printf("server not responding (%s); retrying...", err) time.Sleep(time.Second << uint(tries)) // 指数退避策略 } return fmt.Errorf("server %s failed to respond after %s", url, timeout) } func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "须要提供 url 参数\n") os.Exit(1) } url := os.Args[1] if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) os.Exit(1) } }
这里的指数退避策略,以及尝试屡次简单的超时退出的实现也颇有意思。 操作系统
接着看上面的代码,若是屡次重试后依然不能成功,调用者可以输出错误而后优雅地中止程序,但通常这样的处理应该留给主程序部分:命令行
if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %v\n", err) os.Exit(1) }
一般,若是是库函数,应该将错误传递给调用者,除非这个错误表示一个内部的一致性错误,这意味着库内部存在 bug。
这里还有一个更加方便的方法是经过调用 log.Fatalf 实现上面相同的效果。和全部的日志函数同样,它默认会将时间和日期做为前缀添加到错误消息前:
if err := WaitForServer(url); err != nil { log.Fatalf("Site is down: %v\n", err) }
这种带日期时间的默认格式有助于长期运行的服务器,而对于交互式的命令行工具则意义不大。
还能够自定义命令的名称做为 log 包的前缀,而且将日期和时间略去:
log.SetPrefix("wait: ") log.SetFlags(0)
在一些错误状况下,只记录下错误信息而后程序继续运行。一样地,能够选择使用 log 包来增长日志的经常使用前缀:
if err := Ping(): err != nil { log.Printf("Ping failed: %v; networking disabled", err) }
全部 log 函数都会为缺乏换行符的日志补充一个换行符。
或者是,直接输出到标准错误流:
if err := Ping(): err != nil { fmt.Fprintf(os.Stderr, "Ping failed: %v; networking disabled\n", err) }
没有用 log 函数,因此没有时间日期,固然也不须要。上面说了,对于交互式的命令工具意义不大。
在某些罕见的状况下,还能够直接安全地忽略掉整个日志:
dir, err := ioutil.TempDir("", "scratch") if err != nil { return fmt.Errorf("failed to create temp dir: %v", err) } // 使用临时的目录 os.RemoveAll(dir) // 忽略错误,$TMPDIR 会被周期性删除
调用 os.RemoveAll 可能会失败,但程序忽略了这个错误,缘由是操做系统会周期性地清理临时目录。在这个例子中,有意的抛弃了错误,但程序的逻辑看上去就和忘记去处理同样了。要习惯考虑到每个函数调用可能发生的出错状况,当有意忽略一个错误的时候,要清楚地注释一下你的意图。
以前已经使用过 error 类型了,实际上它是一个接口类型,包含一个返回错误消息的方法:
type error interface { Error() string }
构造 error 最简单的方法是调用 errors.New,它会返回一个包含指定错误消息的新 error 实例。
完整的 errors 包其实只有以下的4行代码:
package errors func New(text string) error { return &errorString{text} } type errorString struct { s string } func (e *errorString) Error() string { return e.s }
底层的 errorString 类型是一个结构体,而不是像其余包里那样定义字符串的别名类型。这主要是为了保护它所表示的错误值无心间的(或者也多是故意的)更新。
定义的 Error 方法是指针方法,而不是值方法。这样每次 New 分配的 error 实例都互不相等,即便是一样的错误值,也是不一样的地址:
fmt.Println(errors.New("TEST") == errors.New("TEST")) // false
这样能够避免好比像 io.EOF 这样重要的错误,与仅仅只是包含一样错误消息的一个错误相等。
直接调用 errors.New 的状况比较少,只在直接能取得错误值的字符串信息的时候使用:
func startCPUProfile(w io.Writer) error { if w == nil { return errors.New("nil File") } return pprof.StartCPUProfile(w) }
更多的状况是会获得一个错误值 err,而咱们能够在这个错误值之上作一点包装,还须要作字符串格式化。有一个更易用的封装函数 fmt.Errorf,它额外还提供了字符串格式化的功能,因此通常都是用这个:
doc, err := html.Parse(resp.Body) if err != nil { return fmt.Errorf("parseing %s as HTML: %v", url, err) }