go语言中error的分类与用法
go语言中error的分类与用法
- 原文引用:极客时间中的课程《Go error处理最佳实践》
- 前言:本文要讨论的就是go中error的基本原理/类型,以及最重要的几个问题:
- go代码开发中因为要处理太多的error,会导致充斥大量的if err!=nil的处理;
- 大量错误日志信息的打印,容易泛滥,且信息可能不完整(如哨兵错误,只有"open fail",没有详细信息)不能很好的追溯;
- 在很多需要判断错误类型中过度关注内容,导致上下层业务耦合严重;
- 日志内容是割裂的,如:a1行打印的日志和b2行的日志实际日志隔了很多行,但实际上函数行为上它们是连续的。
go错误处理的设计理念
- 与其他语言相关设计的比较
- go与c++的区别:go函数可以有多个返回值,通常是func add(a,b int)(int,error)这样的形式,便于判断错误的处理err!=nil就是报错,而c++函数:int add(int a, int b)只能返回单值,就需要根据返回值判断状态,不好区分函数执行结果
- go error与java exception,java的exception主要是基于goto这种实现,exception会带有堆栈信息,需要通过try-catch捕获,使用上和效果来说,exception都比较友好,但是使用格式固定,且容易泛滥打印大量错误信息
- go error与panci的设计
- go error设计的理念是及时处理,一个函数返回error就应该被及时处理,这个error是轻量的
- go panic是程序遇到无法执行的错误时使用,非常不建议当exception使用,尽量少的主动调用panic
- error处理上的一些建议
- 应该能够支撑完整的错误追踪能力,也不应抛过多无用的信息,对调试无用的信息其实就是噪音信息,应该被质疑
- 应该只处理一次(理想情况下),要么能cover住不影响流程而不处理,要么返回err并退出,不应该打印再退出这样两个步骤
go error的分类与特点
- error设计:error are value,就是一个含错误内容值的接口
type error interface {Error() string }
- error的模式
- sentinel error哨兵错误,通常用于等值判断,如io的EOF/ErrShortBuffer等,读文件/缓冲区时返回的错误类型;带来的问题是:
- 加大接口的暴露面积,容易导致循环依赖;
- 依赖error的值进行判断,所以二次封装接口时,不能轻易修改它(会破坏原值),可能只能打印记录
var EOF = errors.New("EOF") var ErrUnexpectedEOF = errors.New("unexpected EOF") var ErrShortBuffer = errors.New("short buffer")
- errors Type 类型错误, 如os.PathError,比哨兵错误的好处是只附加了信息(保留了原上下文),没有破坏原错误,保留了原错误。判断可依赖于if err.(type)==os.PathError也可err.Err的方式 强依赖于错误,对业务侵入大,调用方容易产生强耦合
// os.PathError type PathError struct {Op stringPath stringErr error }func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
- opaque errors不透明错误,调用者不关注它具体内容,只需要if err!=nil判断就可以了,有需要的如net.Error,其中timeOut()和temporary()是开放出去的行为,这样降低被强耦合的可能。但net.Error抛出error可能导致业务耦合(即没有遵循开闭原则)
// An Error represents a network error. type Error interface {errorTimeout() bool // Is the error a timeout?Temporary() bool // Is the error temporary? }type AddrError struct {Err stringAddr string }func (e *AddrError) Error() string {if e == nil {return "<nil>"}s := e.Errif e.Addr != "" {s = "address " + e.Addr + ": " + s}return s }func (e *AddrError) Timeout() bool { return false } func (e *AddrError) Temporary() bool { return false }
- wrap error包装错误,思想:对错误进行包装,会保留原err,只添加包装信息,不会改变原err。第三方包github.com/pkg/errors就很好的实现了这些功能,可以通过wrap()方法,对错误进行包装且带了堆栈信息,通过cause()方法获取原来的错误,直接打印可以将所有信息一次性打印出来,避免了割裂
func main() {_,err := ReadConfig()if err != nil {//org err: *os.PathError open /Users/js/.setting.xml: no such file or directoryfmt.Printf("org err: %T %v\n",errors.Cause(err),errors.Cause(err))// wrap err: could not read config: open fail: open /Users/js/.setting.xml: no such file or directoryfmt.Printf("wrap err: %v\n",err)// 详细堆栈信息fmt.Printf("stack trace: \n %+v\n",err)} }func ReadFile(path string)([]byte,error) {f,err := os.Open(path)if err != nil {// 只对err进行包装,不破坏原错误,携带了附加的错误信息&堆栈信息return nil, errors.Wrap(err,"open fail")}defer f.Close()return nil, err }func ReadConfig() ([]byte, error) {home := os.Getenv("HOME")config,err:=ReadFile(filepath.Join(home,".setting.xml"))// 这里也只直接进行包装return config,errors.WithMessage(err,"could not read config") }
- sentinel error哨兵错误,通常用于等值判断,如io的EOF/ErrShortBuffer等,读文件/缓冲区时返回的错误类型;带来的问题是:
error处理优化的一个案例
- 统计io.Reader读取内容的行数的一个方法,大概实现会是这样的
func CountLines(r io.Reader)(int,error) {var (br = bufio.NewReader(r)lines = 0err error)for {_,err = br.ReadString('\n')//读取一行lines++if err != nil {//遇到错误就退出break}}if err != io.EOF {//如果不是eof错误表面读取出错return 0,nil}return lines,nil }
其中出现了两次错误处理,是因为Reader.ReadString返回的是一个具体的错误,导致调用者需要去处理错误。而改进后的版本:
func CountLines2(r io.Reader)(int,error) {sc := bufio.NewScanner(r)lines := 0for sc.Scan() {lines++}return lines, sc.Err() }
其中Scanner.Scan的方法直接屏蔽了具体错误,只返回行为结构:即scan失败时(包括eof和err)返回false,而Scanner.Err也屏蔽了EOF错误,所以使得此处调用点能显得简洁。对比scanner和reader就是我们设计使应该考虑的,如果上层强依赖结果,可能需要我们返回具体值,反之我们就应该设计得更简洁,降低耦合
- 一个处理HTTP请求的例子
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {_, err := fmt.Fprintf(w, "http/1.1 %d %s\r\n", st.Code, st.Reason)if err != nil {return err}for _, h := range headers {_, err := fmt.Fprintf(w, "%s:%s\r\n", h.Key, h.Value)if err != nil {return err}}if _, err := fmt.Fprintf(w, "\r\n"); err != nil {return err}_, err = io.Copy(w, body)return err }
其中因为要对w进行多次写,调用的是Fprintf这种写方法会返回err,必须要去处理,所以导致一堆err!=nil的处理,这种似乎没有什么好的方法去解决,你可能会说调用一个不会返回错误的方法不就可以了,但如果没有呢,rob pike(go创始人)提供了一种思路:
func WriteResponse2(w io.Writer, st Status, headers []Header, body io.Reader) error {ew := &errWrite{Writer:w}fmt.Fprintf(ew, "http/1.1 %d %s\r\n", st.Code, st.Reason)for _, h := range headers {fmt.Fprintf(ew, "%s:%s\r\n", h.Key, h.Value)}fmt.Fprintf(ew, "\r\n")io.Copy(ew, body)return ew.err }type errWrite struct {io.Writererr error }func (e *errWrite) Write(buf []byte)(int,error) {if e.err != nil {return 0,e.err}n := 0n,e.err = e.Writer.Write(buf)return n,nil }
其中for循环部分原来可以直接retrun的逻辑,会变成最后才return,但如果headers数组小(1万次循环才1毫秒不到),可以说都是小问题。在代码层面上,如果我们errWrite的调用点比较多,那代码的简洁度上的优化是巨大的。
go语言中error的分类与用法相关推荐
- C++语言中std::array的神奇用法总结,你需要知道!
摘要:在这篇文章里,将从各个角度介绍下std::array的用法,希望能带来一些启发. td::array是在C++11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能.也正因此, ...
- c语言常量的正确表示const,C语言中的const和free用法详解
注意:C语言中的const和C++中的const是有区别的,而且在使用VS编译测试的时候.如果是C的话,请一定要建立一个后缀为C的文件,不要是CPP的文件.因为,两个编译器会有差别的. 一.C语言中的 ...
- c语言中if和goto的用法,C语言中if和goto的用法.doc
C语言中if和goto的用法 C语言中,if是一个条件语句,用法??if(条件表达式) 语句如果满足括号里面表达式,表示逻辑为真于是执行后面的语句,否则不执行(表达式为真则此表达式的值不为0,为假则为 ...
- 在c语言中while与do-while,C语言中while /do while语句用法
C语言中while /do while语句用法 C语言while语句的用法 while语句的一般形式为:while(表达式)语句 其中表达式是循环条件,语句为循环体. while语句的语义是:计算表达 ...
- c语言while break用法举例,c语言中continue和break的用法
目前,随着计算机在人们生活和工作中的普及,其教学研究地位也在逐渐提升.C语言是一种计算机程序设计语言,其具有高级语言和汇编语言的特点.下面小编就跟你们详细介绍下c语言中continue和break的用 ...
- c语言do while语句用法6,C语言中while /do while语句用法
摘要 腾兴网为您分享:C语言中while /do while语句用法,仙乐,同程旅游,天猫超市,闪送等软件知识,以及上网本系统,酷我音乐mac,美版微信,地基承载力计算,云解压,猫咪咖啡馆游戏,智课雅 ...
- c++语言中ifndef和endif的用法
1.#ifndef "if not defined"的简写,是宏定义的一种,它是可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等.实际上确切的说这应该是预处理功能中三种 ...
- c语言while函数作用,C语言中while /do while语句用法
在c语言中do while与while与我们学的vb,asp.net都一样的,下面我来介绍一下关于C语言中while /do while语句基于用法. C语言while语句的用法 while语句的一般 ...
- C语言中extern修饰符的用法
在C语言中,修饰符extern用在变量或者函数的声明前,用来说明"此变量/函数是在别处定义的,要在此处引用". 0. extern修饰变量的声明.举例来说,如果文件a.c需要引用b ...
最新文章
- java类载入器——ClassLoader
- SAP SD基本业务总结
- leetcode 861. 翻转矩阵后的得分
- 使用TestContainers进行数据库测试
- java坐标移动题目case_用java怎样编写一个二维坐标平移程序
- LeetCode 696. Count Binary Substrings
- php实现ssh客户端,php无阻塞SSH客户端实例
- HOG特征提取算法的过程
- 开发Servlet的方法(2)
- 离职因多写3个字被索赔2.9万,这家公司的操作让网友直呼拳头摁了......
- log4j和web.xml配置webAppRootKey 的问题(一个tomcat下部署多个应用)
- python编码器用什么意思_通常提到的编码器是干什么用的
- 信息论里的信息熵到底是什么含义?互信息的定义
- 视频教程-ArcGIS与CASS在地籍建库中的结合应用-大数据
- arm9处理器 java_ARM处理器系列介绍
- kernal tch 下载 天正_打开cad图时提示TCH_KERNAL 缺乏解释器天正图形看不见, 请下载天正插件......
- c# [NETSDK1005] havent a target “net48“
- 【自学Python:Day3】放假的心该怎么冷静下来学……
- oracle存储过程语法累加,Oracle 存储过程语法总结及练习
- 课改要实现“软着陆”