在构建任何软件,特别是微服务时,我们应该始终注意一些事情,这些事情无论如何都会发生:错误。
在构建新的软件产品时,失败始终是我们必须考虑的问题,这是领域的一部分,没有办法绕过它,特别是在构建分布式系统时。
问题不在于失败,而在于缺乏对这些失败的监控和反应的计划。
错误介绍
Go中的错误很简单,从某种意义上说,任何实现了错误接口的类型都被认为是错误,错误的概念是检测它们,对它们做一些处理,如果需要的话,把它们泡起来,以便调用者也能对它们做一些处理。
if err := function(); err != nil {
// something happens
return err
}
在Go 1.13中,一些额外的方法被添加到errors 包中,以更好的方式处理识别和处理错误,特别是。
而不是使用== 操作符来比较一个哨兵错误,我们可以使用类似的方法。
if err == io.ErrUnexpectedEOF // Before
if errors.Is(err, io.ErrUnexpectedEOF) // After
而不是显式地做类型断言,我们可以使用这个函数。
if e, ok := err.(*os.PathError); ok // Before
var e *os.PathError // After
if errors.As(err, &e)
- 新
fmt动词%w和errors.Unwrap,其目的是用更多的细节来装饰错误,但仍然保持原始错误的完整性。比如说。
fmt.Errorf("something failed: %w", err)
当看下面实现的代码时,这个errors.Unwrap 函数会更有意义。
用状态实现一个自定义的错误类型
我们的代码实现了一个名为internal.Error 的错误类型,这个新类型包括状态,这个状态的想法是定义一个错误代码,我们可以用它来正确地在我们的HTTP层上呈现不同的响应。这些不同的响应将由包含在错误中的代码决定。
它看起来像这样。
// Error represents an error that could be wrapping another error, it includes a code for determining
// what triggered the error.
type Error struct {
orig error
msg string
code ErrorCode
}
以及支持的错误代码。
const (
ErrorCodeUnknown ErrorCode = iota
ErrorCodeNotFound
ErrorCodeInvalidArgument
)
有了这些类型,我们可以定义一些额外的函数来帮助我们包装原始的错误,例如我们的PostgreSQL存储库,使用WrapErrorf 来包装错误并添加关于发生了什么的额外细节。
return internal.Task{}, internal.WrapErrorf(err, internal.ErrorCodeUnknown, "insert task")
然后,如果这个错误发生了,HTTP层可以对它做出反应,并以正确的状态代码呈现相应的响应。
func renderErrorResponse(w http.ResponseWriter, msg string, err error) {
resp := ErrorResponse{Error: msg}
status := http.StatusInternalServerError
var ierr *internal.Error
if !errors.As(err, &ierr) {
resp.Error = "internal error"
} else {
switch ierr.Code() {
case internal.ErrorCodeNotFound:
status = http.StatusNotFound
case internal.ErrorCodeInvalidArgument:
status = http.StatusBadRequest
}
}
renderResponse(w, resp, status)
}
结论
定义你自己的错误的想法是整合不同的方法来处理它们,给它们添加状态允许我们做出不同的反应;在我们的案例中,这将是根据代码呈现不同的响应;但也许在你的用例中,它可能主要触发不同的警报或发送消息给不同的服务。