zoukankan      html  css  js  c++  java
  • error or panic

    panic

    • 程序启动时,如果有强依赖服务出故障可以panic
    • 程序启动时,配置出错,可以panic
    • 其他情况都不允许该panic,应该返回error
    • 在入口处,gin的中间件有recovery预防panic
    • 在程序中避免使用野生goroutine
      • 如果请求需要执行异步任务,应该使用异步的worker,比如消息通知的方式,避免请求一次创建一个goroutine(虽然net也是这样)
      • 如果必须创建goroutine,那么应该使用一个func统一创建处理,避免野生goroutine panic导致主进程退出
    func Go(f func()) {
    	go func() {
    		defer func() {
    			if err := recover(); err != nil {
    				log.Printf("panic: %+v", err)
    			}
    		}()
    	}()
    }
    

    error

    • 我们在应用程序中使用 github.com/pkg/errors 处理应用错误,注意在公共库当中,我们一般不使用这个

    • error应该是函数的最后一个返回值,当error != nil 的时候,函数不该对其他值有不明确的返回,必须是不可用

      • func f() (io.Reader, *S1, error) , 这个函数就没法对io.Reader做出明确定义
    • 错误处理首先判断错误,出现err != nil 立即返回,避免嵌套处理

    // good case
    func f() error {
        a, err := A()
        if err != nil {
            return err
        }
    
        // ... 其他逻辑
        return nil
    }
    
    // bad case
    func f() error {
        a, err := A()
        if err == nil {
        	// 其他逻辑
        }
    
        return err
    }
    
    • 应用程序出现错误时,使用errors.New活着errors.Errorf返回错误
    func (u *usecese) usecase1() error {
        money := u.repo.getMoney(uid)
        if money < 10 {
            errors.Errorf("用户余额不足, uid: %d, money: %d", uid, money)
        }
        // 其他逻辑
        return nil
    }
    
    • 如果是应用程序的其他函数出错,应该立即返回,如果需要写带信息,使用errors.WithMessage
    func (u *usecese) usecase2() error {
        name, err := u.repo.getUserName(uid)
        if err != nil {
            return errors.WithMessage(err, "其他附加信息")
        }
    
        // 其他逻辑
        return nil
    }
    
    • 如果是调用其他库(标准库,公共库,第三方...)出现错误,请使用 errors.Wrap 添加堆栈信息
      • 不需要每个地方都wrap,只需要在第一次出现的地方进行一次wrap即可
      • 根据场景进行判断是否需要将其他库的原始错误吞掉,例如可以把 repository 层的数据库相关错误吞掉,返回业务错误码,避免后续我们分割微服务或者更换 ORM 库时需要去修改上层代码
      • 注意我们在基础库,被大量引入的第三方库编写时一般不使用 errors.Wrap 避免堆栈信息重复,因为第三方可能也有相关处理
       func f() error {
        err := json.Unmashal(&a, data)
        if err != nil {
            return errors.Wrap(err, "其他附加信息")
        }
    
        // 其他逻辑
        return nil
    } 
    
    • 禁止每个出错地方都打日志,只需要在进程最开始的地方使用%+v,例如http/rpc服务的中间件
    • 错误判断使用errors.Is比较
    func f() error {
        err := A()
        if errors.Is(err, io.EOF){
        	return nil
        }
    
        // 其他逻辑
        return nil
    }
    
    • 错误类型判断,使用errors.As
    func f() error {
        err := A()
    
        var errA errorA
        if errors.As(err, &errA){
        	// ...
        }
    
        // 其他逻辑
        return nil
    }
    
    • 如何判断错误信息足够?想一下当出现错误排查时候是否信息足够,比如请求就需要一些输出参数的信息
    • 对于业务错误,推荐在一个统一地方创建一个错误字典,字典里应该包括错误码(error code),并且在日志中独立打出来,同时要有清晰的错误文档
    • 不需要返回,忽略的错误必须输出到日志信息中
    • 同一个地方不停的报错,尽量避免不停输出日志,可以打印一次日志详情,然后打印错误出现的次数,避免被大量的日志信息淹没别的信息
    • 对于统一类型的错误,采用相同的模式,比如参数错误,不要一个错误码是400,另一个是401
    • 处理错误时,需要主要是否需要处理分配资源,使用defer,或者尽量不适用defer手动是否句柄
    • goroutine 用%+v 打印堆栈详情

    panic or error?

    • 在Go中panic会导致程序直接退出,如果使用panic recovery性能会受影响

      • 性能问题
      • 容易漏掉panic处理,导致主进程意外退出
      • 不可控, 错误交给了外部
    • 什么时候使用panic

      • 对于真正的崩溃情况, 比如索引越界,环境问题,栈溢出,大致就是不可恢复的错误
    • 使用error好处

      • 简单
      • plan for failure not success, 代码逻辑考虑错误情况的处理
      • 没有隐藏
      • 自主可控的错误
      • 错误也是值

    error handle

     // 统计文件行数
    func count(r io.Reader) (int, error) {
    	var (
    		br    = bufio.NewReader(r)
    		lines int
    		err   error
    	)
    
    	for {
    		// 读取到换行符就说明是一行
    		_, err = br.ReadString('
    ')
    		lines++
    		if err != nil {
    			break
    		}
    	}
    
    	// 当错误是 EOF 的时候说明文件读取完毕了
    	if err != io.EOF {
    		return 0, err
    	}
    
    	return lines, err
    }
    
    func count2(r io.Reader) (int, error) {
    	var (
    		sc    = bufio.NewScanner(r)
    		lines int
    	)
    
    	for sc.Scan() {
    		lines++
    	}
    
    	return lines, sc.Err()
    }
    

    error writer

     _, err = fd.Write(p0[a:b])
    if err != nil {
        return err
    }
    _, err = fd.Write(p1[c:d])
    if err != nil {
        return err
    }
    _, err = fd.Write(p2[e:f])
    if err != nil {
        return err
    }
    // and so on
    

    err writer

    type errWriter struct {
        w   io.Writer
        err error
    }
    
    func (ew *errWriter) write(buf []byte) {
        if ew.err != nil {
            return
        }
        _, ew.err = ew.w.Write(buf)
    }
    
    // 使用时
    ew := &errWriter{w: fd}
    ew.write(p0[a:b])
    ew.write(p1[c:d])
    ew.write(p2[e:f])
    // and so on
    if ew.err != nil {
        return ew.err
    }
    

    为什么不需要处处使用errors.Wrap

    因为每一次 errors.Wrap 的调用都会为错误添加堆栈信息,如果处处调用那会有大量的无用堆栈

    func main() {
    	fmt.Printf("err: %+v", c())
    }
    
    func a() error {
    	return errors.Wrap(fmt.Errorf("xxx"), "test")
    }
    
    func b() error {
    	return a()
    }
    
    func c() error {
    	return b()
    }
    

    结果是已经打印出全部的堆栈信息了

    err: xxx
    test
    main.a
            /home/ll/project/Go-000/Week02/blog/wrap.go:14
    main.b
            /home/ll/project/Go-000/Week02/blog/wrap.go:18
    main.c
            /home/ll/project/Go-000/Week02/blog/wrap.go:22
    main.main
            /home/ll/project/Go-000/Week02/blog/wrap.go:10
    runtime.main
            /usr/local/go/src/runtime/proc.go:204
    runtime.goexit
            /usr/local/go/src/runtime/asm_amd64.s:1374
    

    ref
    http://lailin.xyz/post/go-training-03.html

  • 相关阅读:
    9.11 eventbus
    9.10,,,实现new instanceof apply call 高阶函数,偏函数,柯里化
    9.9 promise实现 写完了传到gitee上面了,这里这个不完整
    9.5cors配置代码
    9.5 jsonp 实现
    9.5 http tcp https总结
    9.3 es6 class一部分 and es5 class 发布订阅
    8.30 cookie session token jwt
    8.30vue响应式原理
    warning: LF will be replaced by CRLF in renard-wx/project.config.json. The file will have its original line endings in your working directory
  • 原文地址:https://www.cnblogs.com/CherryTab/p/14133566.html
Copyright © 2011-2022 走看看