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

目录

  • 一、defer
  • 二、错误处理
  • 三、统一错误处理的逻辑
  • 四、panic
  • 五、recover
  • 六、error vs panic
  • 七、错误处理综合示例

一、defer 1. defer保证在函数结束时发生.
2. defer列表为先进后出
3. 参数在defer语句时计算.
下面来看一个例子: 写入文件
package mainimport ("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 fbifunc Feibonaccq() func() int {x, y := 0, 1return func() int {x, y = y, x+yreturn 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 mainimport ("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 mainimport ("io/ioutil""net/http""os")// 我们来模拟一个web服务器. 在url上输入一个地址, 然后显示文件内容// 做一个显示文件的web serverfunc 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)})// 监听端口:8888err := http.ListenAndServe(":8888", nil)iferr != nil {panic("err")}}

这里面主要注意一下我们对错误的处理. 都是直接打出panic. 这样是很不友好的.
如果页面输入的文件路径不对, 则直接404
详解Go语言的错误处理和资源管理
文章图片

按照之前第二步说的, 我们应该对panic进行处理. 比如打开文件的操作, 我们改为如下
// 打开文件file, err := os.Open(path)if err != nil {http.Error(writer, err.Error(), http.StatusInternalServerError)return}defer file.Close()

这样就好多了, 起码程序不会直接抛出异常
详解Go语言的错误处理和资源管理
文章图片

这是将系统的错误直接打出了, 比上面好一些, 但也不是特别友好, 通常我们不希望吧系统内部错误输出出来. 我们希望经过包装后输出错误
于是做了如下修改.
第一步: 将http.handleFunc中的函数部分提出来, 这部分是业务逻辑.
提出来以后做了如下修改. 1. 函数增加一个返回值error. 2. 遇到错误,直接return. 如下红色标出部分
package fileListenerimport ("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// 封装errorfunc 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.StatusOKswitch {case os.IsNotExist(err):code = http.StatusNotFoundcase os.IsPermission(err):code = http.StatusServiceUnavailabledefault:code = http.StatusInternalServerError}http.Error(writer, http.StatusText(code), code)}}}

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

这样, 当我们再次输入错误的文件路径时, 提示信息如下:
详解Go语言的错误处理和资源管理
文章图片


四、panic 发生panic的时候, 会做那些事呢?
1. 停止当前函数的执行
2. 一直向上返回, 执行每一层的defer
3. 如果没有遇到recover, 程序就退出

五、recover 1. 在defer 中调用
2. 获取panic的值
3. 如果无法处理, 可以重新panic
package mainimport ("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 详解Go语言的错误处理和资源管理
文章图片


七、错误处理综合示例 第五条的案例, 我们进行了error的统一管理, 但是还没有对其他异常进行recover, 还有可能导致程序崩溃. 比如http://localhost:8888/abc. 继续优化代码.
详解Go语言的错误处理和资源管理
文章图片

这样很不友好, 我们在看看控制台, 发现程序并没有挂掉, 这是为什么呢? 想象一下, 应该是程序自动给我们recover了.
详解Go语言的错误处理和资源管理
文章图片

我们来看看server.go
详解Go语言的错误处理和资源管理
文章图片

原来server.go已经帮我们recover了, recover后并不是中断进程, 而是打印输出错误日志. 虽然如此, 但页面显示依然很难看. 因此我们要做两件事
1. 如果出现异常, 我们自己进行recover, 那么他就不会走系统定义的recover了. 这还不够, 这只是说控制台不会再打印出一大堆蓝色异常代码了. 我们还有做第二件事
2. 将出现异常的位置捕获出来, 并且, 打印到页面
第一步: 自定一定recover, 代替server.go中的recover
// 封装errorfunc 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.StatusOKswitch {case os.IsNotExist(err):code = http.StatusNotFoundcase os.IsPermission(err):code = http.StatusServiceUnavailabledefault:code = http.StatusInternalServerError}http.Error(writer, http.StatusText(code), code)}}}

这样异常就被我们捕获了, 页面打印出
详解Go语言的错误处理和资源管理
文章图片

这样就好看多了. 我们在对代码进行优化
我们将发生异常的地方进行处理
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}

页面打印效果
详解Go语言的错误处理和资源管理
文章图片

我们发现这个打印的还是系统给出的错误异常. 那么,我们有没有办法, 把这个异常打印出来呢?
if c := strings.Index(request.URL.Path, "/list/"); c != 0 {return errors.New("url 不是已list开头")}

我们自己来定义一个异常处理的接口
type userError interface {error// 系统异常Message() string// 用户自定义异常}

【详解Go语言的错误处理和资源管理】接口定义好了, 在哪里用呢? 你想打印出自己的异常信息, 那就不能打印系统的. 自定义信息在系统异常之前判断
// 执行原来的逻辑. 然后增加error的错误处理err := handler(writer, request)if err != nil {if userErr, ok := err.(userError); ok {http.Error(writer, userErr.Message(), http.StatusBadRequest)return}code := http.StatusOKswitch {case os.IsNotExist(err):code = http.StatusNotFoundcase os.IsPermission(err):code = http.StatusServiceUnavailabledefault:code = http.StatusInternalServerError}http.Error(writer, http.StatusText(code), code)}

接下来是具体实现了, 现在用户想要实现自定义一个userError. 然后设置异常类型为userError
type userError stringfunc (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 handlingimport ("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之后的就是urlif 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 mainimport ("aaa/handlerError/linstenerFile/handling""github.com/siddontang/go/log""net/http""os")type ErrorHandlering func(writer http.ResponseWriter, request *http.Request) errorfunc 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.StatusOKswitch err {case os.ErrNotExist:code = http.StatusNotFoundcase os.ErrPermission:code = http.StatusBadRequestdefault:code = http.StatusInternalServerError}http.Error(writer, http.StatusText(code), code)}}}type UserError interface {errorMessage() string}func main() {// 模拟web请求http.HandleFunc("/", WrapError(handling.Hanldering))// 指定服务端口http.ListenAndServe(":8888", nil)}

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

    推荐阅读