defer 调用
通常用于资源管理,defer可以确保在函数结束时调用
func tryDefer(){
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}
func main() {
tryDefer()
}
看下 打印顺序 可以看出来: defer是可以确保 程序一定会执行的,哪怕中间return 或者panic 都不怕 defer本身的执行顺序 是 先进后出
来看下defer的具体应用
func writeFile(filename string) {
// 创建一个文件
file, err := os.Create(filename)
if err != nil {
panic(err)
}
// 既然open了一个文件 那一定要记得关闭
defer file.Close()
// 往缓存里面写入数据
writer := bufio.NewWriter(file)
// 既然使用了缓存 那么一定要记住要flush
defer writer.Flush()
// 往缓存中写入对应的数据
fmt.Fprintln(writer, "hello golang wuyue111")
}
还是挺好用的,和java相比 这个defer的特性可以确保我们不会出现忘记关闭资源的bug
另外要注意的是参数是在defer语句是进行计算
举例:
func tryDefer() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
if i==5 {
panic("too many times")
}
}
}
看下执行结果:
错误处理
之前的程序 我们可以看出来,panic是直接让程序终止的,就好像类似于java里面 丢了一个没人能处理的异常 直接导致程序运行停止了。正常情况下 我们应该避免这种情况。所以 才需要正确的错误处理方式
比如:
func writeFile2(filename string) {
// 创建一个文件 如果之前已经存在这个文件 那么直接报错
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
fmt.Println("file aleady exists")
}
defer file.Close()
}
这样的提示方式是比较友好的,我们当然可以看看这个error到底是个啥
嗯 就是一个接口,只不过这个接口是一个Error方法
func writeFile2(filename string) {
// 创建一个文件 如果之前已经存在这个文件 那么直接报错
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err.Error())
}
defer file.Close()
}
那到这里呢,我们还可以继续看看这个openfile函数
意思就是 如果这里发生了错误 那一定是一个patherror的指针类型
func writeFile2(filename string) {
// 创建一个文件 如果之前已经存在这个文件 那么直接报错
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
pathError, ok := err.(*os.PathError)
if !ok {
panic(err)
} else {
fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", pathError.Err)
}
}
defer file.Close()
}
那我们继续来看一下:
另外就是 一般情况下,我们错误处理的时候 还会加个return语句 确保不会在后面的程序中 走入错误的逻辑
func writeFile2(filename string) {
// 创建一个文件 如果之前已经存在这个文件 那么直接报错
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
pathError, ok := err.(*os.PathError)
if !ok {
panic(err)
} else {
fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", pathError.Err)
}
// 错误处理的时候别忘记return
return
}
defer file.Close()
}
当然我们也可以自己顺手创建一个error
func writeFile2(filename string) {
// 创建一个文件 如果之前已经存在这个文件 那么直接报错
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
// 当然也可以自己创建一个error
if filename == "test.txt" {
err = errors.New("file name can not be test.txt")
}
if err != nil {
pathError, ok := err.(*os.PathError)
if !ok {
panic(err)
} else {
fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", pathError.Err)
}
// 错误处理的时候别忘记return
return
}
defer file.Close()
}
统一处理错误逻辑
来写个简单的web程序吧
你看我这个下面 有这么多go文件 我希望在浏览器 输入对应的路径的时候 可以看到对应go文件的内容
程序很简单:
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
//看下传来的path
path := request.URL.Path[len("/list/"):]
//找到对应的文件
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()
// 读取文件的内容
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
//将文件的内容 输出
writer.Write(all)
})
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
也可以看到效果:
那如果输入一个错误的路径会得到啥呢?
显然这是不正确的,看下我们的server端:
虽然http库对server的panic做了保护 不会让服务器直接挂掉,但是这里访问的人在浏览器上的体验就极其糟糕了
改进一下:
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
//看下传来的path
path := request.URL.Path[len("/list/"):]
//找到对应的文件
file, err := os.Open(path)
if err != nil {
//改进一下 提供一些正常的错误信息
http.Error(writer, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
// 读取文件的内容
all, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
//将文件的内容 输出
writer.Write(all)
})
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
这样看起来显然正常的多
除此之外 这里的错误处理 显然要改的地方太多,正常情况下 我们应该写一个函数 统一处理
首先我们可以将handle函数 的所有error 都返回出来
func handlerFileListing(writer http.ResponseWriter, request *http.Request) error {
//看下传来的path
path := request.URL.Path[len("/list/"):]
//找到对应的文件
file, err := os.Open(path)
if err != nil {
// 如果有错误 那么直接返回
return err
}
defer file.Close()
// 读取文件的内容
all, err := ioutil.ReadAll(file)
if err != nil {
// 如果有错 那么直接返回
return err
}
//将文件的内容 输出
writer.Write(all)
// 没有错误 显然就返回nil了
return nil
}
为了方便呢,我们给这个函数取一个别名:
// 定义一个函数 返回值为error的 appHandler
type appHandler func(writer http.ResponseWriter, request *http.Request) error
最终呢我们处理一下这个函数的error
// 这个函数的返回值 是一个函数
func errWrapper(handle appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handle(writer, request)
if err != nil {
logger := zap.NewExample()
code := http.StatusOK
switch {
case os.IsNotExist(err):
logger.Warn("handle file not exist")
code = http.StatusNotFound
http.Error(writer, http.StatusText(code), code)
default:
code = http.StatusInternalServerError
}
}
}
}
最终呢 就换一种形式去调用她:
func main() {
http.HandleFunc("/list/", errWrapper(handlerFileListing))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
panic
前面我们见到了数次使用panic的地方,总结一下panic关键字的作用:
停止当前函数的执行 panic会一直向上返回 执行每层的defer 如果没有遇见recover 程序就退出了。
与之对应的还有recover这个关键函数
recover只能在defer调用中使用 并且还可以获取panic的值,无法处理panic的时候可以重新pani
func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("find error",err)
}else{
panic(r)
}
}()
//panic(errors.New("new error"))
panic("just string")
}
可以看下具体两种不同的panic的时候 程序的运行状态
除此之外呢 这里panic是有可能为nil的,所以我们必须进行判空
func tryRecover() {
defer func() {
r := recover()
if r == nil {
fmt.Println("panic is nil")
return
}
if err, ok := r.(error); ok {
fmt.Println("find error", err)
} else {
panic(r)
}
}()
//panic(errors.New("new error"))
//panic("just string")
}
最后总结一下,自己在设计程序的时候:
意料之中的错误 用error,意料之外的用panic