Uber Go语言编码规范( 三 )

vs.
Good
// package foovar ErrCouldNotOpen = errors.New("could not open")func Open() error { return ErrCouldNotOpen}// package barif err := foo.Open(); err != nil { if err == foo.ErrCouldNotOpen { // handle } else { panic("unknown error") }}如果您有可能需要客户端检测的错误 , 并且想向其中添加更多信息(例如 , 它不是静态字符串) , 则应使用自定义类型 。
【Uber Go语言编码规范】Bad
func open(file string) error { return fmt.Errorf("file %q not found", file)}func use() { if err := open(); err != nil { if strings.Contains(err.Error(), "not found") { // handle } else { panic("unknown error") } }}vs.
Good
type errNotFound struct { file string}func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file)}func open(file string) error { return errNotFound{file: file}}func use() { if err := open(); err != nil { if _, ok := err.(errNotFound); ok { // handle } else { panic("unknown error") } }}直接导出自定义错误类型时要小心 , 因为它们已成为程序包公共API的一部分 。最好公开匹配器功能以检查错误 。
// package footype errNotFound struct { file string}func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file)}func IsNotFoundError(err error) bool { _, ok := err.(errNotFound) return ok}func Open(file string) error { return errNotFound{file: file}}// package barif err := foo.Open("foo"); err != nil { if foo.IsNotFoundError(err) { // handle } else { panic("unknown error") }}错误包装(Error Wrapping)
一个(函数/方法)调用失败时 , 有三种主要的错误传播方式:

  • 如果没有要添加的其他上下文 , 并且您想要维护原始错误类型 , 则返回原始错误 。
  • 添加上下文 , 使用"pkg/errors".Wrap以便错误消息提供更多上下文 , "pkg/errors".Cause可用于提取原始错误 。
  • 使用fmt.Errorf , 如果调用者不需要检测或处理的特定错误情况 。
建议在可能的地方添加上下文 , 以使您获得诸如“调用服务foo:连接被拒绝”之类的更有用的错误 , 而不是诸如“连接被拒绝”之类的模糊错误 。
在将上下文添加到返回的错误时 , 请避免使用“ failed to”之类的短语来保持上下文简洁 , 这些短语会陈述明显的内容 , 并随着错误在堆栈中的渗透而逐渐堆积:
Bad
s, err := store.New()if err != nil { return fmt.Errorf( "failed to create new store: %s", err)}failed to x: failed to y: failed to create new store: the errorvs.
Good
s, err := store.New()if err != nil { return fmt.Errorf( "new store: %s", err)}x: y: new store: the error但是 , 一旦将错误发送到另一个系统 , 就应该明确消息是错误消息(例如使用err标记 , 或在日志中以"Failed"为前缀) 。
另请参见Don't just check errors, handle them gracefully.
处理类型断言失败
类型断言的单个返回值形式针对不正确的类型将产生panic 。因此 , 请始终使用“comma ok”的惯用法 。
Bad
t := i.(string)vs.
Good
t, ok := i.(string)if !ok { // 优雅地处理错误}不要panic
在生产环境中运行的代码必须避免出现panic 。panic是级联失败的主要根源。如果发生错误 , 该函数必须返回错误 , 并允许调用方决定如何处理它 。
Bad
func foo(bar string) { if len(bar) == 0 { panic("bar must not be empty") } // ...}func main() { if len(os.Args) != 2 { fmt.Println("USAGE: foo <bar>") os.Exit(1) } foo(os.Args[1])}vs.
Good
func foo(bar string) error { if len(bar) == 0 return errors.New("bar must not be empty") } // ... return nil}func main() { if len(os.Args) != 2 { fmt.Println("USAGE: foo <bar>") os.Exit(1) } if err := foo(os.Args[1]); err != nil { panic(err) }}panic/recover不是错误处理策略 。仅当发生不可恢复的事情(例如:nil引用)时 , 程序才必须panic 。程序初始化是一个例外:程序启动时应使程序中止的不良情况可能会引起panic 。
var _statusTemplate = template.Must(template.New("name").Parse("_statushtml"))即便是在test中 , 也优先使用t.Fatal或t.FailNow来标记test是失败的 , 而不是panic 。
Bad
// func TestFoo(t *testing.T)f, err := ioutil.TempFile("", "test")if err != nil { panic("failed to set up test")}


推荐阅读