美文网首页
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和异常处理

    defer go里有个关键字defer 这两段代码会输出什么呢tryDefer1: 2 1tryDefer1: 3...

  • 异常处理

    异常处理 Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go...

  • 异常处理

    Go 的异常处理不同于其他语言使用的结构 对于 go 的异常处理, 使用 defer, panic, recove...

  • (译)Go 语言中异常处理的艺术

    原文链接:The Art of Error Handling in Go Go 语言的异常处理与其他语言截然不同,...

  • golang的异常处理

    go语言是不支持异常的,go语言的设计者认为异常会被不成熟的程序员滥用,导致异常的处理过去复杂;go语言取消异常的...

  • Go语言探索 - 12(结局)

    Go语言基础系列博客用到的所有示例代码 上一篇文章文章主要学习了Go语言中的接口、反射以及错误和异常处理。本篇文章...

  • Go 语言基础——错误处理

    学习目标 掌握错误处理 掌握自定义错误处理 掌握defer关键字的使用 错误处理 GO没有异常处理机制 Go语言引...

  • Golang 学习笔记八 错误异常

    一、错误异常 《快学 Go 语言》第 10 课 —— 错误与异常Go 语言的异常处理语法绝对是独树一帜,在我见过的...

  • Go语言异常处理

    异常处理 异常处理分为两种提示用户错误信息但不终止程序直接中断程序 提示错误信息(不终止程序) 方法一 fmt.E...

  • go语言异常处理

    序言 异常是一个很重要的语言特性,go也支持异常,但是要支持的更加简洁。怎么学习go的异常,一是学习go的范式,二...

网友评论

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

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