Go基础-错误和异常处理

149 阅读3分钟

总有同学会认为错误和异常是一个概念,其实不然那么错误和异常有什么区别呢?

错误是意料之中,异常是意料之外

错误

1.error

go语言内置的错误类型 error 本质上是接口类型,我们如果需要自定义错误类型,那么也需要将其实现为 error 接口类型,这样调用总是可以通过 Error() 获取到具体的错误信息而不用关心错误的具体类型。标准库的 fmt.Errorferrors.New 可以方便的创建 error 类型的变量。

type error interface {    Error() string}

golang 的多返回值语法糖避免了这种方式带来的不便,错误值一般作为返回值列表的最后一个,其他返回值是成功执行时需要返回的信息。为了避免错误处理时过深的代码缩进:

if err != nil {    // error handling} else {    // normal code}

推荐在发生错误时立即返回:

if err != nil {    // error handling    return // or continue, etc.}// normal code

预定义错误值

预定义错误值,当函数需要返回错误时,每次都可以调用 errors.New()来返回error 的对象:

func Cacl() error {    if one1 {        return errors.New("no space left on the host")    } else {        return errors.New("maybe permission denied")    }}

上面的例子有个问题是不太方便对定义的错误值进行判断,最好的做法是可以先定义一个全局的错误值:

var ErrNoSpace = errors.New("no space left on the host")var ErrPermissionDenied = errors.New("maybe permission denied")​func Cacl() error {    if one1 {        return  ErrNoSpace    } else {        return  ErrPermissionDenied    }}

这样十分利于错误值的判断:

if err == ErrNoSpace {    //return ...}

自定义错误类型

HTTP 表示客户端的错误状态码有很多。如果为每种状态码都需要先定义相应的错误值,代码将会变得十分繁琐:

var ErrBadRequest = errors.New("status code 400: bad request")var ErrUnauthorized = errors.New("status code 401: unauthorized")// ...

这种场景下最佳的最法是自定义一种错误类型,并且至少实现 Error() 方法:

type HTTPError struct {    Code        int    Description string}​func (h *HTTPError) Error() string {    return fmt.Sprintf("status code %d: %s", h.Code, h.Description)}

异常处理

1.defer

defer是什么?

go编写程序时常用defer来充当延迟调用机制的角色,编写在defer后面的语句只能在当前语句执行完成以后才能继续执行,通常也用来进行资源的释放。

defer与栈的结构类似,遵循着先进后出的原则。

补充:defer设计先进后出机制的原因?

防止后申请的资源对依赖前置申请的资源,需要先释放后申请的资源。

defer示例

package mainimport (  "fmt")func main() {  defer func() {    fmt.Println("2")  }()      fmt.Println("1")}

输出结果

12Process finished with exit code 0

当有多个 defer 行为被注册时,它们会以后进先出去执行, 相当于开辟了一个延时调用栈

也基于defer语句可以延迟调用的特性,故而defer可以十分方便的处理资源释放问题。例如:文件的关闭、解锁和时间记录等系列操作。

defer并发延迟解锁

var (    // 初始化map(key为string类型,value为int类型)    map1 = make(map[string]int)    // 使用sync包中提供的Mutex类型来实现互斥锁    m sync.Mutex)// 定义取值函数func ReadValue(k1 string) int {    // 加锁    m.Lock()    // 根据map1中的key取出value    v1 := map1[key]    // 解锁    m.Unlock()    // 返回value    return v1}

使用 defer 语句对上面的语句进行简化,参考下面的代码。

func ReadValue(k1 string) int {    m.Lock()       // defer后面的语句不会被立刻调用执行, 而是会延迟到该函数结束的时候才会被调用    defer m.Unlock()    return map1[key]}

2.panic

函数中遇到panic语句,会立即终止当前函数的执行

例:

package mainimport "fmt"func main() {	fmt.Println("start main")	a()	fmt.Println("end main")}func a() {	fmt.Println("start a")	panic("panic in a")	fmt.Println("end a")}

结果

start mainstart apanic: panic in agoroutine 1 [running]:main.a()	D:/tools/goland/src/book/goshengjing/test11.go:125 +0xa5main.main()	D:/tools/goland/src/book/goshengjing/test11.go:119 +0x8aProcess finished with exit code 2

3.recover

使用recover()去捕获panic()并恢复执行。recover()用于捕捉panic()错误,并返回这个错误信息。

package mainimport "fmt"func main() {	fmt.Println("start main")	a()	fmt.Println("end main")}func a() {	fmt.Println("start a")	defer func() {		if str := recover(); str != nil {			fmt.Println(str)		}	}()	panic("panic in a")	fmt.Println("end a")}

本文正在参加技术专题18期-聊聊Go语言框架