错误行为特征检视策略 #
哨兵错误处理策略和 错误值类型检视策略在错误的构造方与错误处理方两者之间建立了耦合。我们是否还有手段可以降低错误处理方与错误值构造方的耦合呢?
在 Go 标准库中,我们发现了这样一种错误处理方式:将某个包中的错误类型归类,统一提取出一些公共的错误行为特征,并将这些错误行为特征放入一个公开的接口类型中。这种方式也被叫做错误行为特征检视策略。
以标准库中的net包为例,它将包内的所有错误类型的公共行为特征抽象并放入 net.Error这个接口中,如下面代码:
// $GOROOT/src/net/net.go
type Error interface {
error
Timeout() bool
Temporary() bool
}
我们看到,net.Error 接口包含两个用于判断错误行为特征的方法:Timeout 用来判断是否是超时(Timeout)错误,Temporary 用于判断是否是临时(Temporary)错误。
而错误处理方只需要依赖这个公共接口,就可以检视具体错误值的错误行为特征信息,并根据这些信息做出后续错误处理分支选择的决策。
这里,我们再看一个 http 包使用错误行为特征检视策略进行错误处理的例子,加深下理解:
// $GOROOT/src/net/http/server.go
func (srv *Server) Serve(l net.Listener) error {
... ...
for {
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
// 注:这里对临时性(temporary)错误进行处理
... ...
time.Sleep(tempDelay)
continue
}
return e
}
...
}
... ...
}
在上面代码中,Accept 方法实际上返回的错误类型为*OpError,它是 net 包中的一个自定义错误类型,它实现了错误公共特征接口net.Error,如下代码所示:
// $GOROOT/src/net/net.go
type OpError struct {
... ...
// Err is the error that occurred during the operation.
Err error
}
type temporary interface {
Temporary() bool
}
func (e *OpError) Temporary() bool {
if ne, ok := e.Err.(*os.SyscallError); ok {
t, ok := ne.Err.(temporary)
return ok && t.Temporary()
}
t, ok := e.Err.(temporary)
return ok && t.Temporary()
}
因此,OpError 实例可以被错误处理方通过net.Error接口的方法,判断它的行为是否满足 Temporary 或 Timeout 特征。