总有同学会认为错误和异常是一个概念,其实不然那么错误和异常有什么区别呢?
错误是意料之中,异常是意料之外
错误
1.error
go语言内置的错误类型 error 本质上是接口类型,我们如果需要自定义错误类型,那么也需要将其实现为 error 接口类型,这样调用总是可以通过 Error() 获取到具体的错误信息而不用关心错误的具体类型。标准库的 fmt.Errorf
和 errors.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语言框架