• 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

  • 相关阅读:
    Web开发模式演变(转)
    面向切面编程--AOP(转)
    Python装饰器与面向切面编程(转)
    python 线程,GIL 和 ctypes(转)
    Python性能鸡汤(转)
    对Django框架架构和Request/Response处理流程的分析(转)
    django源码笔记-【2】(转)
    django源码笔记-【1】(转)
    PHPCMS9.6.0最新版SQL注入和前台GETSHELL漏洞分析 (实验新课)
    从零开始学习渗透Node.js应用程序
  • 原文地址:https://www.cnblogs.com/CherryTab/p/14133566.html
Copyright © 2020-2023  润新知