美文网首页
go 语言学习12--defer和异常处理

go 语言学习12--defer和异常处理

作者: 神奇大叶子 | 来源:发表于2018-08-12 11:56 被阅读18次

    defer

    go里有个关键字defer

    func tryDefer1() {
        defer fmt.Println(1)
        fmt.Println(2)
    }
    
    func tryDefer2() {
        defer fmt.Println(1)
        defer fmt.Println(2)
        fmt.Println(3)
    }
    

    这两段代码会输出什么呢
    tryDefer1: 2 1
    tryDefer1: 3 2 1

    所以 defer有两个特点

    • 在函数退出了才执行,甚至前面有 return 也行
    • 按照defer的顺序从后往前执行(类似栈)

    这种特性很方便,可以用在很多资源释放的场合

    • Open/Close
    • Lock/Unlock
    • PrintHeader/PrintFooter

    异常处理

    go里没有那么麻烦的 try catch,scala里同样不推崇传统的java那种异常捕获,感觉这是个新的思维,还得多学习学习
    举个🌰

    func writeFile(filename string) {
        file, err := os.Create(filename)
        if err != nil {
            panic(err)
        }
        defer file.Close()
        write := bufio.NewWriter(file)
        defer write.Flush()
    
        f := fib.Fib()
        for i := 0; i < 20; i++ {
            fmt.Fprint(write, strconv.Itoa(f())+"\n")
        }
    }
    

    我想把 fib 数写进一个文件里,有经验的同学都知道,打开一个文件需要捕获异常,因为这个文件可能不存在,或者写入因为各种权限问题时报错
    go里很多函数都是两个返回值,第一个是结果,第二个是异常

    go很方便在于,你打开一个文件,你就顺手 defer close掉,不用特意包裹在麻烦的catch
    用一个bufio写效率会高很多,记得要 flush到磁盘里
    Fprint接收一个 writer

    再来看个异常处理的例子
    打开一个文件,都知道文件可能不存在会抛出异常

    image
    不直接panic
    http.Error 有三个参数,第一个是 write 也就是你的网页,第二个是出错信息,第三个是 code
    随便输错一个再来看
    image
    可是这样也不太好,程序的报错不应该暴露给用户,只需要让用户知道 not found 就行了

    把整个 handler 函数抽出来

    package filelisting
    
    import (
        "net/http"
        "os"
        "io/ioutil"
    )
    
    func Handler(writer http.ResponseWriter, request *http.Request) error {
        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)
        return nil
    }
    

    返回值是 error
    定义一个结构体,对应这个handler 函数

    type appHandler func(writer http.ResponseWriter, request *http.Request) error
    

    下面用到函数式编程的思想
    传这个 error 进去,但是返回 http.HandleFunc 需要的函数
    有点像python的装饰器

    func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            err := handler(writer, request)
            if err != nil {
                log.Warn("Error handling request: %s",
                    err.Error())
                var code int
                switch {
                case os.IsNotExist(err):
                    code = http.StatusNotFound
                case os.IsPermission(err):
                    code = http.StatusForbidden
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
            }
        }
    }
    

    一个装饰 error 的函数,返回也是一个函数
    在函数体内包装 error
    整个程序最后是这样的

    package main
    
    import (
        "net/http"
        "golearn/lesson11/filelistserver/filelisting"
        "os"
        "github.com/gpmgo/gopm/modules/log"
    )
    
    type appHandler func(writer http.ResponseWriter, request *http.Request) error
    
    func errWrapper(handler appHandler) func(writer http.ResponseWriter, request *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            err := handler(writer, request)
            if err != nil {
                log.Warn("Error handling request: %s",
                    err.Error())
                var code int
                switch {
                case os.IsNotExist(err):
                    code = http.StatusNotFound
                case os.IsPermission(err):
                    code = http.StatusForbidden
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
            }
        }
    }
    
    func main() {
        http.HandleFunc("/list/", errWrapper(filelisting.Handler))
        err := http.ListenAndServe(":8888", nil)
        if err != nil {
            panic(err)
        }
    }
    
    image

    最后页面返回就是想要的结果,而不是直接暴露错误给用户

    recover

    说道panic就要说recover

    • 仅在 defer 调用中使用
    • 获取 panic 的值
    • 如果无法处理,可重新 panic
      看代码
    
    func tryRecover() {
        defer func() {
            r := recover()
            if err, ok := r.(error); ok {
                fmt.Println("Error occurred:", err)
            } else {
                panic(r)
            }
        }()
        panic(errors.New("this is a new error"))
    }
    

    panicrecover有点像try catch其实就是c语言的try catch
    我的程序出错了(panic)但是我不想让他让程序终止,在我可控的范围内处理他(recover)
    当我发现他是我知道的类型(error),我处理他,如果是我意料之外的东西,我还可以继续panic

    上面的 http 服务器的例子都是系统异常直接抛出了,实际上,预料的异常应该 recover,还要自定义一些异常作为给用户看的

    image
    image
    在 Handler 这个方法的文件里,实现 userError 这个接口,抛出一个用户异常
    详细代码看github

    总结

    • 异常处理要用到 defer,panic,recover
    • go是互联网时代的c,panic,recover都是c过来的思想,其实就是try,catch
    • 意料之中的错误用 err, 意想不到的问题才 panic(尽量不要用)

    上述代码均已上传至 github, 欢迎 star
    https://github.com/yejunyu/golearn


    image

    相关文章

      网友评论

          本文标题:go 语言学习12--defer和异常处理

          本文链接:https://www.haomeiwen.com/subject/lylsbftx.html