如果调用者需要询问错误(例如,区分不同的错误条件),请给出错误值结构,这样可以通过编程完成,而不是让调用者进行字符串匹配。这个建议适用于生产代码,也适用于关心不同错误条件的测试。
最简单的结构化错误是无参数的全局值。
type Animal stringvar ( // ErrDuplicate occurs if this animal has already been seen. ErrDuplicate = errors.New("duplicate") // ErrMarsupial occurs because we're allergic to marsupials outside Australia. // Sorry. ErrMarsupial = errors.New("marsupials are not supported"))func pet(animal Animal) error { switch { case seen[animal]: return ErrDuplicate case marsupial(animal): return ErrMarsupial } seen[animal] = true // ... return nil}
调用者可以简单地将函数返回的错误值与已知的错误值之一进行比较:
// Good:func handlePet(...) { switch err := process(an); err { case ErrDuplicate: return fmt.Errorf("feed %q: %v", an, err) case ErrMarsupial: // Try to recover with a friend instead. alternate = an.BackupAnimal() return handlePet(..., alternate, ...) }}
上面使用了哨兵值,其中误差必须等于(在==
的意义上)预期值。这在很多情况下是完全足够的。如果process
返回包装好的错误(下面讨论),你可以使用errors.Is
。
// Good:func handlePet(...) { switch err := process(an); { case errors.Is(err, ErrDuplicate): return fmt.Errorf("feed %q: %v", an, err) case errors.Is(err, ErrMarsupial): // ... }}
不要试图根据字符串的形式来区分错误。(参见Go Tip #13: 设计用于检查的错误以了解更多信息)。
// Bad:func handlePet(...) { err := process(an) if regexp.MatchString(`duplicate`, err.Error()) {...} if regexp.MatchString(`marsupial`, err.Error()) {...}}
如果错误中有调用者需要的额外信息,最好是以结构化方式呈现。例如,os.PathError
类型的记录是将失败操作的路径名,放在调用者可以轻松访问的结构域中。
其他错误结构可以酌情使用,例如一个包含错误代码和细节字符串的项目结构。Package status
是一种常见的封装方式;如果你选择这种方式(你没有义务这么做),请使用 规范错误码。参见 Go Tip #89: 何时使用规范的状态码作为错误 以了解使用状态码是否是正确的选择。