Go基础-错误和异常处理

69 阅读3分钟

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

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

错误

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 main

import (  "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 main

import "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 a
panic: panic in a

goroutine 1 [running]:
main.a()
	D:/tools/goland/src/book/test123.go:14 +0xa5
main.main()
	D:/tools/goland/src/book/test123.go:8 +0x8a

Process finished with exit code 2

3.recover

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

package main

import "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语言框架\

\