错误包装(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 error
vs.
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”)
}
vs.
Good
// func TestFoo(t *testing.T)
f, err := ioutil.TempFile("", “test”)
if err != nil {
t.Fatal(“failed to set up test”)
}
使用sync/atomic包的原子操作对原始类型(int32,int64等)进行操作(译注:指atomic包的方法名中均使用原始类型名,如SwapInt32等),因此很容易忘记使用原子操作来读取或修改变量。
go.uber.org/atomic通过隐藏基础类型为这些操作增加了类型安全性。此外,它包括一个方便的atomic.Bool类型。
Bad
type foo struct {
running int32 // atomic
}
func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// already running…
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // race!
}
vs.
Good
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
// already running…
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running.Load()
}
三. 性能
性能方面的特定准则,适用于热路径。
优先使用strconv而不是fmt
将原语转换为字符串或从字符串转换时,strconv速度比fmt快。
Bad
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
vs.
Good
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
避免字符串到字节的转换
不要反复从固定字符串创建字节slice。相反,请执行一次转换并捕获结果。
Bad
for i := 0; i < b.N; i++ {
w.Write([]byte(“Hello world”))
}
BenchmarkBad-4 50000000 22.2 ns/op
vs.
Good
data := []byte(“Hello world”)
for i := 0; i < b.N; i++ {
w.Write(data)
}
BenchmarkGood-4 500000000 3.25 ns/op