• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    详解Go语言的错误处理和资源管理

    一、defer

    1. defer保证在函数结束时发生.

    2. defer列表为先进后出

    3. 参数在defer语句时计算.

    下面来看一个例子: 写入文件

    package main
    
    import (
        "aaa/functional/fbi"
        "bufio"
        "fmt"
        "os"
    )
    
    // 我要写文件
    func writeFile() {
        file, err := os.Create("test.txt")
        if err != nil {
            panic("error")
        }
        defer file.Close()
    
        w := bufio.NewWriter(file)
        defer w.Flush()
    
        f := fbi.Feibonaccq()
        for i := 0; i  20; i++  {
            fmt.Fprintln(w, f())
        }
    
    }
    func main() {
        writeFile()
    }
    package fbi
    
    func Feibonaccq() func() int {
        x, y := 0, 1
        return func() int {
            x, y = y, x+y
            return x
        }
    }

    将斐波那契数列写入文件. 这里有两个资源使用. 1. 创建文件, 然后文件关闭. 2. 写入资源, 将资源从缓存中刷入文件. 这两个操作都应该应该是成对出现的, 因此, 用defer 语句, 避免后面写着写着忘了, 也保证即使出错了, 也能够执行defer语句的内容

    那么参数在defer语句时计算 是什么意思呢?

    func tryDefer() {
        for i := 0; i  10 ; i++ {
            defer fmt.Println(i)
        }
    }

    打印结果:

    9

    8

    7

    6

    5

    4

    3

    2

    1

    0

    二、错误处理

    所谓的错误处理, 就是处理已知的错误, 不要抛出panic这样导致系统挂掉的错误发生.

    比如下面的操作:

    package main
    
    import (
        "aaa/functional/fbi"
        "bufio"
        "fmt"
        "os"
    )
    
    // 我要写文件
    func writeFile(filename string) {
        // os.O_EXCL|os.O_CREATE创建一个新文件, 并且他必须不存在
        file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
        // 这时候打印panic就不太友好. 我们可以对错误类型进行处理
        /*if err != nil {
            panic("error")
        }*/
    
        // 这里就对错误的类型进行了捕获处理.
        if err, ok := err.(*os.PathError); !ok {
            fmt.Println("未知错误")
        } else {
            fmt.Printf("%s, %s, %s", err.Path, err.Op, err.Err)
        }
    
        defer file.Close()
    
        w := bufio.NewWriter(file)
        defer w.Flush()
    
        f := fbi.Feibonaccq()
        for i := 0; i  20; i++  {
            fmt.Fprintln(w, f())
        }
    
    }
    func main() {
        writeFile("test.txt")
    }

    红色字体部分就是对错误进行了捕获处理.

    三、统一错误处理的逻辑

    下面模拟一个web服务器, 在浏览器地址栏输入文件的url, 然后显示文件的内容. 比如斐波那契数列的文件

    package main
    
    import (
        "io/ioutil"
        "net/http"
        "os"
    )
    
    // 我们来模拟一个web服务器. 在url上输入一个地址, 然后显示文件内容
    // 做一个显示文件的web server
    func main() {
        http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
            // 获取url路径, 路径是/list/之后的部分
            path := request.URL.Path[len("/list/"):]
            // 打开文件
            file, err := os.Open(path)
            if err != nil {
                panic("err")
            }
            defer file.Close()
    
            // 读出文件
            b, err := ioutil.ReadAll(file)
            if err != nil {
                panic("err")
            }
    
            // 写入文件到页面
            writer.Write(b)
        })
    
        // 监听端口:8888
        err := http.ListenAndServe(":8888", nil)
        if  err != nil {
            panic("err")
        }
    }

    这里面主要注意一下我们对错误的处理. 都是直接打出panic. 这样是很不友好的.

    如果页面输入的文件路径不对, 则直接404

    按照之前第二步说的, 我们应该对panic进行处理. 比如打开文件的操作, 我们改为如下

    // 打开文件
    file, err := os.Open(path)
    if err != nil {
        http.Error(writer, err.Error(), http.StatusInternalServerError)  return
    }
    defer file.Close()

    这样就好多了, 起码程序不会直接抛出异常

    这是将系统的错误直接打出了, 比上面好一些, 但也不是特别友好, 通常我们不希望吧系统内部错误输出出来. 我们希望经过包装后输出错误

    于是做了如下修改.

    第一步: 将http.handleFunc中的函数部分提出来, 这部分是业务逻辑.

    提出来以后做了如下修改. 1. 函数增加一个返回值error. 2. 遇到错误,直接return. 如下红色标出部分

    package fileListener
    
    import (
        "io/ioutil"
        "net/http"
        "os"
    )
    
    func FileHandler(writer http.ResponseWriter, request *http.Request) error{
        // 获取url路径, 路径是/list/之后的部分
        path := request.URL.Path[len("/list/"):]
        // 打开文件
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
    
        // 读出文件
        b, err := ioutil.ReadAll(file)
        if err != nil {
            return err
        }
    
        // 写入文件到页面
        writer.Write(b)
        return nil
    }

    第二: 封装错误内容

    这里就体现了函数式编程的特点, 灵活

    // 定义一个函数类型的结构, 返回值是erro 
    type Handler func(writer http.ResponseWriter, request *http.Request) error
    
    // 封装error
    func WrapHandler(handler Handler) func (http.ResponseWriter, *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            // 执行原来的逻辑. 然后增加error的错误处理
            err := handler(writer, request)
            if err != nil {
                code := http.StatusOK
                switch {
                case os.IsNotExist(err):
                    code = http.StatusNotFound
    
                case os.IsPermission(err):
                    code = http.StatusServiceUnavailable
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
            }
        }
    }

    调用的部分

    // 我们来模拟一个web服务器. 在url上输入一个地址, 然后显示文件内容
    // 做一个显示文件的web server
    func main() {
        http.HandleFunc("/list/", WrapHandler(fileListener.FileHandler))
    
        // 监听端口:8888
        err := http.ListenAndServe(":8888", nil)
        if  err != nil {
            panic("err")
        }
    }

    这样, 当我们再次输入错误的文件路径时, 提示信息如下:

    四、panic

    发生panic的时候, 会做那些事呢?

    1. 停止当前函数的执行

    2. 一直向上返回, 执行每一层的defer

    3. 如果没有遇到recover, 程序就退出

    五、recover

    1. 在defer 中调用

    2. 获取panic的值

    3. 如果无法处理, 可以重新panic

    package main
    
    import (
        "fmt"
        "github.com/pkg/errors"
    )
    
    func tryRecover() {
    
        defer func(){
            r := recover()
            if r, ok := r.(error); ok {
                fmt.Println("error 发生", r.Error())
            } else {
                panic(fmt.Sprintf("未知错误:%v", r))
            }
        }()
        panic(errors.New("错误"))
    
    }
    
    func main() {
        tryRecover()
    }

    六、error vs panic

    七、错误处理综合示例

    第五条的案例, 我们进行了error的统一管理, 但是还没有对其他异常进行recover, 还有可能导致程序崩溃. 比如http://localhost:8888/abc. 继续优化代码.

    这样很不友好, 我们在看看控制台, 发现程序并没有挂掉, 这是为什么呢? 想象一下, 应该是程序自动给我们recover了.

    我们来看看server.go

    原来server.go已经帮我们recover了, recover后并不是中断进程, 而是打印输出错误日志. 虽然如此, 但页面显示依然很难看. 因此我们要做两件事

    1. 如果出现异常, 我们自己进行recover, 那么他就不会走系统定义的recover了. 这还不够, 这只是说控制台不会再打印出一大堆蓝色异常代码了. 我们还有做第二件事

    2. 将出现异常的位置捕获出来, 并且, 打印到页面

    第一步: 自定一定recover, 代替server.go中的recover

    // 封装error
    func WrapError(handler Handler) func (http.ResponseWriter, *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            defer func(){
                if r := recover(); r != nil {
                    fmt.Println("发生错误")
                    http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                }
            }()
    
            // 执行原来的逻辑. 然后增加error的错误处理
            err := handler(writer, request)
            if err != nil {
                code := http.StatusOK
                switch {
                case os.IsNotExist(err):
                    code = http.StatusNotFound
    
                case os.IsPermission(err):
                    code = http.StatusServiceUnavailable
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
            }
        }
    }

    这样异常就被我们捕获了, 页面打印出

    这样就好看多了. 我们在对代码进行优化

    我们将发生异常的地方进行处理

    func FileHandler(writer http.ResponseWriter, request *http.Request) error {
        // 获取url路径, 路径是/list/之后的部分
        if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
            return errors.New("url 不是已list开头")
        }
        path := request.URL.Path[len("/list/"):]
        // 打开文件
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
    
        // 读出文件
        b, err := ioutil.ReadAll(file)
        if err != nil {
            return err
        }
    
        // 写入文件到页面
        writer.Write(b)
        return nil
    }

    页面打印效果

    我们发现这个打印的还是系统给出的错误异常. 那么,我们有没有办法, 把这个异常打印出来呢?

    if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
        return errors.New("url 不是已list开头")
    }

    我们自己来定义一个异常处理的接口

    type userError interface {
        error        // 系统异常
        Message() string    // 用户自定义异常
    }

    接口定义好了, 在哪里用呢? 你想打印出自己的异常信息, 那就不能打印系统的. 自定义信息在系统异常之前判断

    // 执行原来的逻辑. 然后增加error的错误处理
    err := handler(writer, request)
    if err != nil {
        if userErr, ok := err.(userError); ok {
            http.Error(writer, userErr.Message(), http.StatusBadRequest)
            return
        }
        code := http.StatusOK
        switch {
        case os.IsNotExist(err):
            code = http.StatusNotFound
    
        case os.IsPermission(err):
            code = http.StatusServiceUnavailable
        default:
            code = http.StatusInternalServerError
        }
        http.Error(writer, http.StatusText(code), code)
    }

    接下来是具体实现了, 现在用户想要实现自定义一个userError. 然后设置异常类型为userError

    type userError string
    
    func (u userError) Error() string{
        return u.Message()
    }
    
    func (u userError) Message() string {
        return string(u)
    }
    func FileHandler(writer http.ResponseWriter, request *http.Request) error {
        // 获取url路径, 路径是/list/之后的部分
        if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
            return userError("url 不是已list开头")
        }
        path := request.URL.Path[len("/list/"):]
        // 打开文件
        file, err := os.Open(path)
        if err != nil {
            return err
        }
        defer file.Close()
    
        // 读出文件
        b, err := ioutil.ReadAll(file)
        if err != nil {
            return err
        }
    
        // 写入文件到页面
        writer.Write(b)
        return nil
    }

    这样一个实现自定义打印异常的功能就做好了. 异常也是可以封装的.

    最后再来梳理这个小案例:

    1. 我们有一个想法, 模拟web请求, 在浏览器url上输入一个文件路径, 打印文件的内容

    2. 内容可能有错误, 进行异常处理.

    3. 有时候异常抛出的是系统给出, 我们自己对异常进行recover, 然后打印出来

    4. 打印自定义异常.

    以下是完整代码

    package handling
    
    import (
        "io/ioutil"
        "net/http"
        "os"
        "strings"
    )
    
    type UserError struct {
        Content string
    }
    
    func (u UserError) Error() string {
        return u.Message()
    }
    
    func (u UserError) Message() string {
        return u.Content
    }
    
    func Hanldering(writer http.ResponseWriter, request *http.Request) error {
        // 获取url, list之后的就是url
        if s := strings.Index(request.URL.Path, "/list/"); s != 0 {
            return UserError{"path error, /list/"}
        }
        url := request.URL.Path[len("/list/"):]
    
        // 根据url打开文件
        file, err := os.Open(url)
        if err != nil {
            return os.ErrNotExist
        }
        defer file.Close()
    
        // 打开以后把文件内容读出来
        f, err := ioutil.ReadAll(file)
        if err != nil {
            return os.ErrPermission
        }
    
        // 读出来以后, 写入到页面
        writer.Write(f)
        return nil
    }
    package main
    
    import (
        "aaa/handlerError/linstenerFile/handling"
        "github.com/siddontang/go/log"
        "net/http"
        "os"
    )
    
    type ErrorHandlering func(writer http.ResponseWriter, request *http.Request) error
    
    func WrapError(handler ErrorHandlering) func(http.ResponseWriter, *http.Request) {
        return func(writer http.ResponseWriter, request *http.Request) {
            defer func() {
                if r := recover(); r != nil {
                    log.Warn("other error")
                    http.Error(writer, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
                }
            }()
    
            err := handler(writer, request)
    
            //自定义异常处理
    
            // 错误处理
            if err != nil {
                if userErr, ok := err.(UserError); ok {
                    log.Warn("user error:", userErr.Message())
                    http.Error(writer, userErr.Message(), http.StatusBadRequest)
                    return
                }
                code := http.StatusOK
                switch err {
                case os.ErrNotExist:
                    code = http.StatusNotFound
                case os.ErrPermission:
                    code = http.StatusBadRequest
                default:
                    code = http.StatusInternalServerError
                }
                http.Error(writer, http.StatusText(code), code)
    
            }
        }
    }
    
    type UserError interface {
        error
        Message() string
    }
    
    
    
    
    func main() {
        // 模拟web请求
        http.HandleFunc("/", WrapError(handling.Hanldering))
    
        // 指定服务端口
        http.ListenAndServe(":8888", nil)
    }

    以上就是详解Go语言的错误处理和资源管理的详细内容,更多关于Go 错误处理 资源管理的资料请关注脚本之家其它相关文章!

    您可能感兴趣的文章:
    • 详解Go多协程并发环境下的错误处理
    • 关于Mongodb参数说明与常见错误处理的总结
    • Golang巧用defer进行错误处理的方法
    • Go语言中更优雅的错误处理
    • GO语言标准错误处理机制error用法实例
    • Django静态资源部署404问题解决方案
    • Django跨域资源共享问题(推荐)
    • 基于Django静态资源部署404的解决方法
    • Django静态资源URL STATIC_ROOT的配置方法
    上一篇:分析Go语言接口的设计原则
    下一篇:详解Go语言运用广度优先搜索走迷宫
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    详解Go语言的错误处理和资源管理 详解,语言,的,错误,处理,