网站首页 文章专栏 go语言让人又爱又恨的error
go语言让人又爱又恨的error

一. Go中的error是什么,怎么实现的

1. go的源码实现

go的error其实很简单,就是一个普通的接口,一个普通的值。

我们看下源码:

package errors

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
   return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
   s string
}

func (e *errorString) Error() string {
   return e.s
}

我们一般使用 errors.New()来返回一个error对象。实际error就是一个字符串,使用errorString进行保存,值得注意的是,new方法中返回的是内部errorString对象的指针。也就是如果用两个一摸一样的错误字符串,那么比较的时候也是不相同的,保证每个错误都是独立唯一的。


2. 那么它与java的exception有啥区别呢?

java中进入checked exception,方法的所有者必须申明,调用者必须处理。使用try catch对异常进行处理,导致java里面异常变得很常见,从普通的异常,到严重的异常都有,很多人经常就会使用一个Exception对象直接捕获,然后忽略,或者仅仅只是打个log。

catche( Exception e) { // ignore }

而go中由于支持多参数返回,它没进入exception,可以在函数签名带上实现error interface的对象,由调用者来进行判断怎么处理异常。类似这样:

func handle()(int,error){
    return 1,nil
}

func handleError()(int,error){
    return 1,errors.New("出现错误")
}

那么我们在调用这个函数的时候,就会获得一个error对象,通过判断 err != nil 就可以由调用者判断是否出现了异常。如果一个函数返回了(value, error),那么就不能对error忽略,不能对value做任何假设,必须先判定error的情况,唯一可以忽略的是,如果根本不关心value的时候。

go中还存在panic的机制,panic和异常不同,异常仅仅表示出问题了,返回给调用者进行处理,而panic意味着fatal error,不能假设调用者来解决panic,也就是挂了,代码跑不下去了。某种意义上panic才代表了真正的‘异常’。panic我们可以使用recovery来进行兜底,牺牲单请求,让我们程序可以继续跑下去,一般也不做逻辑处理,或者其他处理,顶多打个log之类的。

二. error的基本用法

比如我们要写一个函数,判断一个数字是正数还是负数。

func main() {
   Check(1)
   Check(-1)
}

func Check(num int) {
   if Positive(num) {
      fmt.Println("为正数")
   } else {
      fmt.Println("为负数")
   }
}

func Positive(num int) bool {
   return num > -1
}

如上很简单的一个函数,bool返回一个值是否是正数,但是问题来了,当入参为0时,我们没有处理,因为0既不是正数也不是负数。那么我们可以用多参数返回改写下:

func main() {
   Check(1)
   Check(-1)
   Check(0)
}

func Check(num int) {
   positive, ok := Positive(num)
   if !ok {
      fmt.Println("既不是正数,也不是负数")
      return
   }
   if positive {
      fmt.Println("为正数")
   } else {
      fmt.Println("为负数")
   }
}

func Positive(num int) (res bool,ok bool) {
   if num == 0 {
      return false, false
   }
   return num > -1, true
}

通过多参数返回,先对特殊值进行处理,也就是参数校验,就可以了,但是好像也不是太好,我们可以用异常再处理下。

func Check(num int) {
   positive, err := Positive(num)
   if err != nil {
      fmt.Println("既不是正数,也不是负数")
      return
   }
   if positive {
      fmt.Println("为正数")
   } else {
      fmt.Println("为负数")
   }
}

func Positive(num int) (res bool,err error) {
   if num == 0 {
      return false, errors.New("undefined")
   }
   return num > -1, nil
}

虽然看着没啥太大区别,但是语义上更直观了,出现不是期待的值时,就算是异常。当然还有种做法,就返回一个值,如果为nil就代表参数错误,如果不为nil再获取值,通过值来获取结果,实际上这种写法是非常不建议的,因为这个值就有了二义性,不再仅仅表示一个含义,在业务上会导致很多麻烦,比如我们在dao层,到底返回一个nil表示找不到对象,还是返回空数组表示呢?这个也是个仁者见仁智者见智的问题,我的理解是返回nil表示找不到对象,因为用空数组表示找不到对象的话,那么如果返回对象就应该为空数组,就会语义就不对了。


还有种写法,使用panic + recovery来处理:

func Check(num int) {
   defer func() {
      if recover() != nil {
         fmt.Println("既不是正数,也不是负数")
         return
      }
   }()
   if Positive(num) {
      fmt.Println("为正数")
   } else {
      fmt.Println("为负数")
   }
}

func Positive(num int) bool {
   if num == 0 {
      panic("undefined")
   }
   return num > -1
}

这种写法也是非常不推荐的,因为panic + recovery虽然看起来能模拟java里try catch的写法,但是go中panic就不是用来表示异常的,panic仅仅表示系统崩了,跑不下去了。对于真正意外的情况,那些表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题(缺少配置文件)、栈溢出,我们才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定。

三. go使用这种方式有什么好处呢

通过上面我们能看出go中error的基本使用方法,很多人吐槽的点也是代码中会大量充斥这 if err != nil 这种写法,让人觉得不爽,那么它和java相比有什么好处呢

我们在java里可能会见到这样的代码:

public User AddNewUser(String userName) {
    User user = new User(userName);
    SaveUser(user);
    AddToGroup(user);
    return user;
}

java中的异常可能会从任意一行抛出,它是不确定的,当我们在执行 saveUser函数时,如果发生了异常,就需要全局异常捕获再处理,而go则会立马在这个函数返回的时候去处理,相当于对saveUser这一行进行try catch单独处理,看起来好像没啥区别,但是体现go的思想是不一样的,go更鼓励对异常立即处理,尽可能让代码语义原子性,强行去细粒度的处理异常,而不是像java那样可以用一个大的try去包裹大量代码然后忽略,或者抛出去不管。

而且java的try catch也使得java代码从try一下跳到catch里面,有的人就用这个来做控制流程的业务了,这一点我觉得也是不太好的,我记得刚学java的时候就记得一点,永远不要用异常来去控制业务流程。而go细粒度的立即处理是没有隐藏控制流的。

总结下go的异常特点:

- 简单,就是一个interface,里面有个字符串保存异常信息

- 考虑失败,而不是成功(plan for failure, not success)

- 没有隐藏的控制流

- 完全交给你来控制 error

- Error are values









版权声明:本文由星尘阁原创出品,转载请注明出处!

本文链接:http://www.52xingchen.cn/detail/97




赞助本站,网站的发展离不开你们的支持!
来说两句吧
大侠留个名吧,或者可以使用QQ登录。
: 您已登陆!可以继续留言。
最新评论