刘沙河 刘沙河
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希

花开半夏,半夏花开
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希
  • go语言基础

  • go语言进阶

    • go 泛型
    • go条件编译
    • 分布式从ACID、CAP、BASE的理论推进
    • go链接参数 ldflags
    • TCP网络连接以及TIME_WAIT的意义
    • Go异常处理
      • panic和recover使用场景
        • 1. panic
        • 2. recover
      • Error vs Exception
      • Error Type (处理错误的方式)
        • 1. Sentinel Error
        • 2. Error types
        • 3. Opaque errors (推荐使用!!)
      • Handling Error
      • Errors
      • Warp Error(!!!)
        • 1. pkg/errors
        • 2. pkg/errors 使用技巧
      • 处理error的正确姿势
        • 失败的原因只有一个时,不使用error
        • 没有失败不使用error
        • error应放在返回值类型列表的最后
        • 错误值统一定义
        • 错误逐层传递时,层层都加日志
        • 当尝试几次可以避免失败时,不要立即返回错误
        • 当上层函数不关心错误时,建议不返回error
        • 当发生错误时,不忽略有用的返回值
      • 处理异常的正确姿势
        • 在程序开发阶段,坚持速错
        • 在程序部署后,应恢复异常避免程序终止
        • 对于不应该出现的分支,使用异常处理 panic
        • 针对入参不应该有问题的函数,使用panic设计
    • Go性能调优 pprof
    • Go语言设计模式
    • Go 切片的截取
    • Go runtime详解
    • go执行外部命令
    • 标准库container三剑客:head、list、ring
    • go与http代理
    • Go内存管理
    • Go垃圾回收机制
    • Go语言中的并发编程
    • Go协程调度原理及GPM模型
    • Go中逃逸现象, 变量+堆栈
    • Go面向对象的思维理解interface
    • Go中的Defer
    • Go和Python中的深浅拷贝
    • Go语言内存对齐
    • 流和IO多路复用
    • 单点Server的N种并发模型汇总
    • 控制goroutine的数量
    • 配置管理库—Viper
    • 高性能日志库zap
    • Go中的Mutex和RWMutex.md
    • sqlx的使用
    • 分布式id 库snowflake和sonyflake
    • sync.Pool 复用对象
    • sync.Once 单例模式
    • sync.Cond 条件变量
    • unsafe.Pointer 和 uintptr
    • go 信号量
    • go语言代码优化技巧
    • go 接口型函数
    • 位运算
    • cgo教程
    • go调用lib和so动态库
  • go语言实现原理

  • gin框架

  • gorm

  • go测试

  • Go语言
  • go语言进阶
bigox
2021-11-27
目录

Go异常处理

# panic和recover使用场景

# 1. panic

程序报错退出,返回码是os.exit(0)

  • 对于真正意外的情况,那些表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题、栈溢出,我们才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定。
  • 速错推荐panic

# 2. recover

从 panic 恢复

tip场景: 别人写的panic 但是不符合你的场景, 你想恢复 ,使用recover

func main() {
  // defer 先进后出
	defer func() {
		data := recover()

		if data!=nil{
			fmt.Println("recover",data) // recover 34
		}
	}()
	panic("34")
  // 以下代码不再执行
  fmt.Println("不执行")
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Error vs Exception

  • Panic使用场景

    对于真正意外的情况,那些表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题、栈溢出,我们才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定。

  • Golang error 处理方式的优点

    • 简单
    • 考虑失败,而不是成功(plan for failure, not success)
    • 没有隐藏的控制流
    • 完全交给你来控制 error
    • Error are values

# Error Type (处理错误的方式)

# 1. Sentinel Error

  • 预定义的特定错误,我们叫为 sentinel error,这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。所以对于 Go,我们使用特定的值来表示错误。 if err == ErrSomething { … } 类似的 io.EOF,更底层的 syscall.ENOENT。

结论:

  • 使用 sentinel 值是最不灵活的错误处理策略,因为调用方必须使用 == 将结果与预先声明的值进行比较。当您想要提供更多的上下文时,这就出现了一个问题,因为返回一个不同的错误将破坏相等性检查。
  • 不依赖检查 error.Error 的输出。不应该依赖检测 error.Error 的输出,Error 方法存在于 error 接口主要用于方便程序员使用,但不是程序(编写测试可能会依赖这个返回)。这个输出的字符串用于记录日志、输出到 stdout 等。
  • 尽可能避免 sentinel errors。

# 2. Error types

  • Error type 是实现了 error 接口的自定义类型。例如 MyError 类型记录了文件和行号以展示发生了什么。

    image-20211127212753991

    结论:

    尽量避免使用 error types,虽然错误类型比 sentinel errors 更好,因为它们可以捕获关于出错的更多上下文,但是 error types 共享 error values 许多相同的问题。 因此,我的建议是避免错误类型,或者至少避免将它们作为公共 API 的一部分。

# 3. Opaque errors (推荐使用!!)

  • 这是最灵活的错误处理策略,因为它要求代码和调用者之间的耦合最少。

    • 这种风格称为不透明错误处理,因为虽然您知道发生了错误,但您没有能力看到错误的内部。作为调用者,关于操作的结果,您所知道的就是它起作用了,或者没有起作用(成功还是失败)。
    • 这就是不透明错误处理的全部功能–只需返回错误而不假设其内容。

    image-20211127213048595

# Handling Error

  • 读取文件多少行

    func CountLines(r io.Reader) (int,error){
      sc := bufio.NewScanner(r)
      lines := 0
      for sc.Scan(){
        lines++
      }
      return lines , sc.Err()
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

# Errors

  • err := errors.New(string) //创建errors
  • err := errors.Errorf(string) //创建errors并记录堆栈信息, 通过'%v'获取
  • err2 := fmt.Errorf("我错了%w", err) // 包装error
  • err0 := errors.Unwrap(err2) //解包装error
  • resBool := errors.Is(err2,err0) // 判断是不是同一个error
  • errors.As(err2,&err3) //类型转为特定的error

# Warp Error(!!!)

  • 使用 pkg/errors 包

  • 我们经常发现类似的代码,在错误处理中,带了两个任务: 记录日志并且再次返回错误。

    image-20211127214609119

    应该只处理一次错误。处理错误意味着检查错误值,并做出单一决策。

# 1. pkg/errors

image-20211127215235143

package main

import (
	"fmt"
	"github.com/pkg/errors"
)

func main() {
	r := errors.New("我错了")
	r2 := errors.Wrapf(r,"我真的错了")
	fmt.Println(r2.Error())
}
1
2
3
4
5
6
7
8
9
10
11
12

# 2. pkg/errors 使用技巧

  1. 在你的应用代码中,使用 errors.New 或者 erros.Errorf 返回错误。其中erros.Errorf 可以记录堆栈信息.

    import (
    	"fmt"
    	"github.com/pkg/errors"
    )
    
    func main() {
    	r := errors.New("我错了")
      //r := erros.Errorf("我错了")
    	r2 := errors.Wrapf(r,"我真的错了")
    	fmt.Println(r2.Error()) // 我真的错了: 我错了
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  2. 如果调用其他包内的函数,通常简单的直接返回。

  3. 直接返回错误,而不是每个错误产生的地方到处打日志。

  4. 在程序的顶部或者是工作的 goroutine 顶部(请求入口),使用 %+v 把堆栈详情记录。

    import (
    	"fmt"
    	"github.com/pkg/errors"
    )
    
    func main() {
    	r := errors.Errorf("我错了")
    	r2 := errors.Wrapf(r,"我真的错了")
    	fmt.Printf("%v",r2.Error())
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 处理error的正确姿势

  1. # 失败的原因只有一个时,不使用error

    案例:

    func (self *AgentContext) CheckHostType(host_type string) error {
        switch host_type {
        case "virtual_machine":
            return nil
        case "bare_metal":
            return nil
        }
        return errors.New("CheckHostType ERROR:" + host_type)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    重构一下代码:

    func (self *AgentContext) IsValidHostType(hostType string) bool {
        return hostType == "virtual_machine" || hostType == "bare_metal"
    }
    
    1
    2
    3

    说明:大多数情况,导致失败的原因不止一种,尤其是对I/O操作而言,用户需要了解更多的错误信息,这时的返回值类型不再是简单的bool,而是error。

  2. # 没有失败不使用error
    // 错误示例
    func (self *CniParam) setTenantId() error {
        self.TenantId = self.PodNs
        return nil
    }
    
    1
    2
    3
    4
    5
  3. # error应放在返回值类型列表的最后
  4. # 错误值统一定义

    很多人写代码时,到处return errors.New(value),而错误value在表达同一个含义时也可能形式不同,比如“记录不存在”的错误value可能为:

    - "record is not existed."
    - "record is not exist!"
    - "###record is not existed!!!"
    
    1
    2
    3

    这使得相同的错误value撒在一大片代码里,当上层函数要对特定错误value进行统一处理时,需要漫游所有下层代码,以保证错误value统一,不幸的是有时会有漏网之鱼,而且这种方式严重阻碍了错误value的重构。

    于是,我们可以参考C/C++的错误码定义文件,在Golang的每个包中增加一个错误对象定义文件,如下所示:

    var ERR_EOF = errors.New("EOF")
    var ERR_CLOSED_PIPE = errors.New("io: read/write on closed pipe")
    var ERR_NO_PROGRESS = errors.New("multiple Read calls return no data or error")
    var ERR_SHORT_BUFFER = errors.New("short buffer")
    var ERR_SHORT_WRITE = errors.New("short write")
    var ERR_UNEXPECTED_EOF = errors.New("unexpected EOF")
    
    1
    2
    3
    4
    5
    6
  5. # 错误逐层传递时,层层都加日志

    层层都加日志非常方便故障定位。不过存在争议!

  6. # 当尝试几次可以避免失败时,不要立即返回错误
  7. # 当上层函数不关心错误时,建议不返回error

    对于一些资源清理相关的函数(destroy/delete/clear),如果子函数出错,打印日志即可,而无需将错误进一步反馈到上层函数,因为一般情况下,上层函数是不关心执行结果的,或者即使关心也无能为力,于是我们建议将相关函数设计为不返回error。

  8. # 当发生错误时,不忽略有用的返回值

    通常,当函数返回non-nil的error时,其他的返回值是未定义的(undefined),这些未定义的返回值应该被忽略。然而,有少部分函数在发生错误时,仍然会返回一些有用的返回值。比如,当读取文件发生错误时,Read函数会返回可以读取的字节数以及错误信息。对于这种情况,应该将读取到的字符串和错误信息一起打印出来。

# 处理异常的正确姿势

  1. # 在程序开发阶段,坚持速错

    速错推荐panic

  2. # 在程序部署后,应恢复异常避免程序终止
  3. # 对于不应该出现的分支,使用异常处理 panic
    switch s := suit(drawCard()); s {
        case "Spades":
        // ...
        case "Hearts":
        // ...
        case "Diamonds":
        // ... 
        case "Clubs":
        // ...
        default:
            panic(fmt.Sprintf("invalid suit %v", s))
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  4. # 针对入参不应该有问题的函数,使用panic设计
    func MustCompile(str string) *Regexp {
        regexp, error := Compile(str)
        if error != nil {
            panic(`regexp: Compile(` + quote(str) + `): ` + error.Error())
        }
        return regexp
    }
    
    1
    2
    3
    4
    5
    6
    7
#Go#
上次更新: 2023/04/16, 18:35:33
TCP网络连接以及TIME_WAIT的意义
Go性能调优 pprof

← TCP网络连接以及TIME_WAIT的意义 Go性能调优 pprof→

最近更新
01
go与http代理
05-24
02
自制申威架构k8s-reloader
12-06
03
Docker Buildx 教程
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2024 小刘扎扎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式