为什么80%的码农都做不了架构师?>>>

error

error类型的声明可在builtin包中查看:

type error interface {Error() string
}

如果直接使用这个,我们需要声明一个结构体然后实现Error()方法,常用的是errors包中的New():

// New returns an error that formats as the given text.
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
}

Go中error类型的nil值和nil

问题描述

func retErr() error {var r *MyError = nilif err() {r = &MyError{}}return r
}
func main() {err := retErr()if err != nil {fmt.Println("error:", err)} else {fmt.Println("no error.")}
}

这里retErr()返回的err并不是nil.

在底层,interface作为两个成员实现:一个类型和一个值。该值被称为接口的动态值, 它是一个任意的具体值,而该接口的类型则为该值的类型。

只有在内部值和类型都未设置时(nil, nil),一个接口的值才为 nil。特别是,一个 nil 接口将总是拥有一个 nil 类型。若我们在一个接口值中存储一个 int 类型的指针,则内部类型将为 int,无论该指针的值是什么:(*int, nil)。 因此,这样的接口值会是非 nil 的,即使在该指针的内部为 nil。

所以,上面的err是一个有效值(非nil),值为 nil。

处理方式一

retErr()在返回值时,如果要返回nil,就直接return nil。

处理方式二

main()在判断时这样:

if err.(*MyError) != nil

返回错误信息

import ("errors""fmt"
)func main() {ret, err := error_test(false)if err != nil {fmt.Println("err is ", err, "ret is ", ret)} else {fmt.Println("ret is ", ret, "err is ", err)}
}func error_test(flag bool) (ret string, err error) {if flag {err = errors.New("error test")return} else {return "success", nil}
}

若传递true,结果为err is error test ret is

若传递false,结果为ret is success err is <nil>

自定义错误类型

Go语言引入了一个关于错误处理的标准模式,即error接口,该接口的定义如下:

type error interface{ Error() string
}

下面红色部分代码参考src\pkg\os\error.go

import ("errors""fmt"
)func main() {err := my_error()if err != nil {fmt.Println("err is ", err)} else {fmt.Println("err is nil")}
}
func my_error() error {_, err := error_test(true)if err != nil {return &PathError{"test", "path", err}} else {return nil}
}func error_test(flag bool) (ret string, err error) {if flag {err = errors.New("error test")return} else {return "success", nil}
}type PathError struct {Op   stringPath stringErr  error
}func (e *PathError) Error() string {return e.Op + " " + e.Path + ": " + e.Err.Error()
}

defer 延迟函数

它的作用是:延迟执行,在声明时不会立即执行,而是在函数return后时按照后进先出的原则依次执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行到,这样就能做很多我们想做的事,如释放资源,清理数据,记录日志等

func CopyFile(dst, src string) (w int64, err error) {srcFile, err := os.Open(src)if err != nil {return}defer srcFile.Close()dstFile, err := os.Create(dstName)if err != nil {return}defer dstFile.Close()return io.Copy(dstFile, srcFile)
}

即使其中的Copy()函数抛出异常,Go仍然会保证dstFile和srcFile会被正常关闭。

如果觉得一句话干不完清理的工作,也可以使用在defer后加一个匿名函数的做法:

defer func() {// 做你复杂的清理工作
}()

defer语句的调用是遵照先进后出的原则,即最后一个defer语句将最先被执行


下面说明一下defer执行的顺序

func deferFunc() int {index := 0fc := func() {fmt.Println(index, "匿名函数1")index++defer func() { fmt.Println(index, "匿名函数1-1")index++}()}defer func() { fmt.Println(index, "匿名函数2")index++}()defer fc()return func() int {fmt.Println(index, "匿名函数3")index++return index}()
}func main() {ret := deferFunc()fmt.Println("main() :", ret)
}

执行结果:

0 匿名函数3
1 匿名函数1
2 匿名函数1-1
3 匿名函数2
main() : 1
  1. defer是在执行完return后执行
  2. return的结果并没有随着index递增
  3. 按照先进后出的顺序执行

返回语句之后的defer不会执行

func main() {i := truedefer func() {fmt.Println("f1")}()defer func() {fmt.Println("f2")}()if i {return}defer func() {fmt.Println("f3")}()
}

输出: f2 f1,没有f3


被延期执行的函数,它的参数(包括接收者,如果函数是一个方法)是在defer执行的时候被求值的,而不是在调用执行的时候。这样除了不用担心变量随着函数的执行值会改变,这还意味着单个被延期执行的调用点可以延期多个函数执行。这里有一个简单的例子

for i := 0; i < 3; i++ {defer fmt.Print(i, " ")
}

输出:2 1 0


验证defer的函数的参数是在defer定义的地方被求值的

func enter(s string) string {fmt.Println("entering ", s)return s
}
func leave(s string) {fmt.Println("leaving ", s)
}
func a() {defer leave(enter("a"))fmt.Println("in a...")b()
}
func b() {defer leave(enter("b"))fmt.Println("in b...")
}

main()中调用a()

输出:

entering  a
in a...
entering  b
in b...
leaving  b
leaving  a

被延迟的函数是leave,它的参数是enter的返回值,所以在定义defer的地方先求了enter的返回值,上面defer的结果等同于下面的语句:

enter(“a”)
defer leave(“a”)

defer中改变返回值

  f := func() (ret int) {defer func() {ret++}()return 1}v := f()fmt.Println(v)

输出2.

panic和recover

func panic(interface{})
func recover() interface{}

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报告,包括在调用panic()函数时传入的参数,这个过程称为错误处理流程

panic类似于抛出异常,recover类似于捕获异常

假如执行:

func() {panic("panic text.")
}()

程序将抛出错误然后退出,但如果这样:

defer func() {if r := recover(); r != nil {fmt.Println("recover text: ", r)}
}()func() {panic("panic text.")
}()fmt.Println("panic之后的语句")

程序执行的结果为:

recover text:  panic text.

panic()之后,程序就退出函数了,之后的语句不会执行了,除了defer

如果没有panic,上面定义的defer也会在函数退出时执行。

看一下 json 包中的一段代码

func (d *decodeState) unmarshal(v interface{}) (err error) {defer func() {if r := recover(); r != nil {if _, ok := r.(runtime.Error); ok {panic(r) // 这里}err = r.(error)}}()rv := reflect.ValueOf(v)if rv.Kind() != reflect.Ptr || rv.IsNil() {return &InvalidUnmarshalError{reflect.TypeOf(v)}}d.scan.reset()// We decode rv not rv.Elem because the Unmarshaler interface// test must be applied at the top level of the value.d.value(rv)return d.savedError
}

这个实现中,我们可以获知几个用法:

  • 在恢复时,通过函数的命名返回值可以返回错误信息
  • 通过类型断言可以判断是运行时错误还是调用了 panic 函数
  • 在恢复时,可以继续调用 panic

错误处理的冗余

func init() {http.HandleFunc("/view", viewRecord)
}func viewRecord(w http.ResponseWriter, r *http.Request) {c := appengine.NewContext(r)key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)record := new(Record)if err := datastore.Get(c, key, record); err != nil {http.Error(w, err.Error(), 500)return}if err := viewTemplate.Execute(w, record); err != nil {http.Error(w, err.Error(), 500)}
}

每次发生错误都需要调用http.Error(w, err.Error(), 500),还可能有更复杂多处理逻辑。

通过复用检测函数来减少类似的代码

type appHandler func(http.ResponseWriter, *http.Request) errorfunc (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {if err := fn(w, r); err != nil {http.Error(w, err.Error(), 500)}
}

这里的appHandler和上面的viewRecord实现了相同的接口,用appHandler来包装viewRecord。

注册时这样:

func init() {http.Handle("/view", appHandler(viewRecord))
}

然后viewRecord就可以这样写:

func viewRecord(w http.ResponseWriter, r *http.Request) error {c := appengine.NewContext(r)key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)record := new(Record)if err := datastore.Get(c, key, record); err != nil {return err}return viewTemplate.Execute(w, record)
}

其实是将错误处理的共有代码封装到了appHandler函数中。

提供更友好的错误提示

自定义错误类型

type appError struct {Error   errorMessage stringCode    int
}

这样我们的自定义路由器可以改成如下方式:

type appHandler func(http.ResponseWriter, *http.Request) *appErrorfunc (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {if e := fn(w, r); e != nil { // e is *appError, not os.Error.c := appengine.NewContext(r)c.Errorf("%v", e.Error)http.Error(w, e.Message, e.Code)}
}

这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式:

func viewRecord(w http.ResponseWriter, r *http.Request) *appError {c := appengine.NewContext(r)key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)record := new(Record)if err := datastore.Get(c, key, record); err != nil {return &appError{err, "Record not found", 404}}if err := viewTemplate.Execute(w, record); err != nil {return &appError{err, "Can't display record", 500}}return nil
}

转载于:https://my.oschina.net/u/2004526/blog/847174

Go笔记-错误处理和defer相关推荐

  1. golang框架gin的日志处理和zap lumberjack日志使用

    目录 gin自带日志 新建logger.go mian.go 引用 zap lumberjack接管gin日志 新建logger.go main.go 调用 gin框架好用,轮子也多,我也来丰富下内容 ...

  2. unity 3分钟理解 批处理和drawcall有什么区别

    3分钟理解 批处理和drawcall有什么区别? 本人技术有限,如有错误,请道友们留言讨论,切勿口吐芬芳. 正文-------------------------------------------- ...

  3. 千牛包表包下载_带有服务器端处理和VueJS组件的数据表包

    千牛包表包下载 Vue数据表 (Vue Data Table) Data Table package with server-side processing and VueJS components. ...

  4. [Python从零到壹] 三十七.图像处理基础篇之图像融合处理和ROI区域绘制

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  5. 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理

    阅读react-redux源码 - 零 阅读react-redux源码 - 一 阅读react-redux源码(二) - createConnect.match函数的实现 阅读react-redux源 ...

  6. 告别手动输入验证码!Web自动化测试带你解锁验证码处理和Cookie机制,跨越测试瓶颈!

    Web自动化之验证码处理及cookie机制 在Web自动化测试中,验证码的处理一直是一个难点.如果没有自动化处理方式,手动输入验证码将会非常耗时且容易出错.本文将为大家介绍如何通过Python实现验证 ...

  7. [Python从零到壹] 三十三.图像处理基础篇之什么是图像处理和OpenCV配置

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  8. Django笔记十二之defer和only

    本篇笔记将介绍查询中的 defer 和 only 两个函数的用法,笔记目录如下: defer only 1.defer defer 的英语单词的意思是 延迟.推迟,我们可以通过将字段作为参数传入,可以 ...

  9. JSP错误页面的处理和exception对象

    exception对象可以使用的主要方法如下所示. l        getMessage():该方法返回错误信息. l        printStackTrace():该方法以标准错误的形式输出一 ...

最新文章

  1. Oracle 统计信息(1)
  2. 使用IDEA2017创建java web +maven项目
  3. ITK:可视化静态密集2D水平集零集
  4. 『飞秋』小项目心得交流
  5. 动态瑜伽 静态瑜伽 初学者_瑜伽与编程有什么关系?
  6. NumberUtils的 isParsable(String)和isCreatable(String)方法
  7. 【解释】while(~scanf(%d, n))的~的含义~scanf
  8. [BZOJ 4403]序列统计(Lucas定理)
  9. 计算机组成原理白中英第五版之指令系统
  10. mPaSS小程序 路由跳转
  11. tv盒子管理助手android版本,TV盒子工具 管理电视盒子的好助手
  12. Dev5.4.0由于与64位的版本不兼容的问题解决方案
  13. 信息系统项目管理师(2022年)—— 重点内容:项目变更管理(16)
  14. scratch编程三级--小猫和笔合作画正方形
  15. 道路驾驶技能计算机评判项目,2017最新科目二和科目三考试评判标准变动情况...
  16. Linux之线程条件变量cond
  17. Vista下最好用输入法 - 搜狗拼音输入法4.0正式版闪亮登场!
  18. db2 日期英式写法_英式与美式日期写法 基数与序数词辨析
  19. 离心泵水力设计——叶轮设计——2 前后盖板型线
  20. WebSocket无法连接问题

热门文章

  1. VB6 通过winsock控件数组实现客户端和服务器多对一通信
  2. 获取当前的系统时间 年-月-日 小时-分钟-秒
  3. for..in与for..of比较
  4. WIN2008系统的IIS7.0配置REWRITE伪静态环境
  5. centos 6.8 64B mini origin vm file
  6. 从Netty到EPollSelectorImpl学习Java NIO
  7. 表面积最小(POJ3536)
  8. IOS 6.0+ Autolayout — UITableViewCell 高度调整
  9. 自定义弹出框控件制作及示例
  10. Know about RDBMS market share