Go笔记-错误处理和defer
2017-02-27 00:00
423 查看
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 = nil if 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 string Path string Err 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 := 0 fc := 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
defer是在执行完return后执行
return的结果并没有随着index递增
按照先进后出的顺序执行
返回语句之后的defer不会执行
func main() { i := true defer 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) error func (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 error Message string Code int }
这样我们的自定义路由器可以改成如下方式:
type appHandler func(http.ResponseWriter, *http.Request) *appError func (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 }
相关文章推荐
- go基本语法学习笔记之错误处理
- Go圣经-学习笔记之函数和错误处理
- go错误处理——return,panic,defer
- go语言学习笔记(10) 错误处理
- Go学习笔记(九)接口,错误处理
- Windows核心编程——笔记:第一章 错误处理
- BDA驱动学习笔记(6):错误处理,内存管理以及字符串
- WCF RIA Services学习笔记四 客户端错误处理
- 【SQL Server学习笔记】存储过程、sp_executesql存储过程、try catch错误处理
- Go_Error(str) 错误返回处理
- Oracle学习笔记[3] - 错误:ora-12154:tns:无法处理服务名的错误
- Go 语言的错误处理机制引发争议
- JavaScript高级程序设计 错误处理与调试学习笔记
- ASP.NET 3.5核心编程学习笔记(8):错误处理、页面跟踪
- JavaScript高级程序设计 阅读笔记(二十) js错误处理
- 学习笔记--- 使用客户端脚本以自定义错误处理
- StringTemplate.Net 学习笔记(11):自定义错误处理
- Javascript 学习笔记 错误处理
- 32、深入理解计算机系统笔记,Unix下处理错误的风格
- 调试的艺术学习笔记--程序崩溃处理(段错误)