在解析了 Golang中error和创建error的源码后( Golang学习——error和创建error源码解析)。

对error有了一定理解,不过error处理才是实际开发中非常重要的一点。

Golang中的error处理是一门大学问,写出优雅又正确的处理代码是比较考验编码功底和知识广度,深度的。

今天就先浅谈一下Golang中的错误处理。

一.error与类型错误的变量进行比较

1.== 比较

直接进行比较也是一种方式,但是有种硬编码的感觉,必须事先确定好错误类型或已经知道要发生的错误是什么类型的,这样在错误比较的时候才能处理得当。

让我们通过一个例子来理解这个问题。

filepathGlobErrBadPattern
filepathErrBadPattern
var ErrBadPattern = errors.New("syntax error in pattern")
errors.New()ErrBadPattern

实战看一下就明白:

files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
	fmt.Println("error:", error)
	return
}
fmt.Println("matched files:", files)

输出:

error: syntax error in pattern
==True
errorsyntax error in pattern

但是这种方式有个问题:就是这些错误,往往是提前约定好的,而且处理起来不太灵活。

path/filepath

当然这是标准库的包,还能接受。如果很多用户自定义的包都定义了错误,那我们就要引入很多包,来判断各种错误,这容易引起循环引用的问题。

不过这种比较的优点就是错误界限比较清楚,能够清晰的知道到底是什么错误

2.contains 比较

contains
func openFile(path string) error {
	_, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("cannot open file, err:", err)
	}
	return nil
}

func main(){
	err := openFile("./test.txt")
	if strings.Contains(error.Error(), "not found") {
		// handle error
	}
}
error.Error()
二.断言底层结构类型,并从结构体字段获取更多信息

通过类型断言来判断error是哪种类型的错误,通常指的是那些实现了 error 接口的类型。

这些类型一般都是结构体,除了error字段外,还有其他字段,提供了额外的信息。

我们看一个实例:

type PathError struct {
	Op   string
	Path string
	Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
PathErrorError()

我们验证一下这些字段的输出内容是什么:

f, err := os.Open("./test.txt")
if err, ok := err.(*os.PathError); ok {
	fmt.Printf("err.Op -> %s \n", err.Op)
	fmt.Printf("err.Path -> %s\n", err.Path)
	fmt.Printf("err.Err -> %v\n", err.Err)
	return
}
fmt.Println(f.Name(), "打开成功")

输出:

err.Op -> open
err.Path -> ./test.txt
err.Err -> The system cannot find the file specified.

通常,使用这样的 error 类型,外层调用者需要使用类型断言来判断错误。

if elseswitch case

这样的做的话,无形中会导入很多外部的包,容易引起循环引用,不太推荐。

三.断言底层类型的行为

断言底层类型的行为,通常指的是调用struct类型的方法来获取更多信息。

举个例子,查看 DNSError 源码:

// DNSError represents a DNS lookup error.
type DNSError struct {
	Err         string // description of the error
	Name        string // name looked for
	Server      string // server used
	IsTimeout   bool   // if true, timed out; not all timeouts set this
	IsTemporary bool   // if true, error is temporary; not all errors set this
	IsNotFound  bool   // if true, host could not be found
}

func (e *DNSError) Timeout() bool { return e.IsTimeout }

func (e *DNSError) Temporary() bool { return e.IsTimeout || e.IsTemporary }
DNSErrorTimeout()Temporary()

实战一下:

addrs, err := net.LookupHost("www.bucunzaide.com")

if err != nil {
	if ins, ok := err.(*net.DNSError); ok {
		if ins.IsTimeout {
			fmt.Println("链接超时......")
		} else if ins.IsTemporary {
			fmt.Println("暂时性错误......")
		} else if ins.IsNotFound {
			fmt.Printf("链接无法找到......,err:%v\n", err)
		} else {
			fmt.Println("未知错误......", err)
		}
	}
	return
}
fmt.Println("访问成功,地址为:", addrs)

输出:

链接无法找到......,err:lookup www.bucunzaide.com: no such host
DNSError

这样做的好处是不需要 import 引用定义错误的包,因为判断就是结构体的方法(已经引用过包了),比较推荐这种方式。

总结一下,今天主要记录了处理错误的基本三种方式,以后还会一直跟进错误处理这个话题,学习了更好更优雅的处理方式后会再记录。