自学内容网 自学内容网

【Golang】Go语言编程思想(三):资源管理和出错处理

资源管理与出错处理

Golang 通过 defer 调用来确保调用在函数结束或 panic 时发生,从而进行资源管理。

对语句使用 defer 关键字,则这条语句将会在函数结束时才运行,比如:

package main

import "fmt"

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

func main() {
tryDefer()
}

输出的结果是:

3
2
1

一个更典型的例子如下,在该例当中我们实现了一个函数,用于向给定文件当中写入前二十个 Fibonacci 数:

func writeFile(filename string) {
file, err := os.Create(filename)
if err != nil {
panic(err)
}
defer file.Close() // 打开的 file 使用 defer 进行 close

// 直接读写文件比较慢, 使用 bufio 来对文件进行读写
writer := bufio.NewWriter(file)
defer writer.Flush() // 将 buffer 当中的内容写入到文件

f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
// 函数结束时, 将会首先运行 writer.Flush(), 再运行 file.Close()
}

何使使用 defer 调用?

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

错误处理

现在我们对之前的 writeFile 函数进行修改,修改的结果如下:

func writeFile(filename string) {
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer file.Close() // 打开的 file 使用 defer 进行 close

// 直接读写文件比较慢, 使用 bufio 来对文件进行读写
writer := bufio.NewWriter(file)
defer writer.Flush() // 将 buffer 当中的内容写入到文件

f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(writer, f())
}
}

👆 如果此时要写入的目标文件存在,那么程序会在第一个 panic 挂掉。但是我们不希望程序只给出出错的信息,我们希望在函数执行的过程中给出错误信息,并试图对错误进行修正,或是将函数返回以避免函数进一步执行得到不理想的结果:

if err != nil {
// fmt.Println("file already exists")
fmt.Println("Error:", err.Error())
return
}

现在我们关心的是,err 当中本身包含哪些内容。根据 Golang 提供的文档,得知 err 本质上是一个 *PathError 类型。可以通过下述方式得到 err 当中包含哪些内容:

if err != nil {
//fmt.Println("file already exists")
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Println(pathError.Op, pathError.Path, pathError.Err)
}
return
}

得到的结果如下:

open fib.txt The file exists.

其中 open 是 pathError.Op,fib.txt 是 pathError.Path,The file exists 是 PathError.Err。

我们自己也可以新建一些 error,比如:

err = errors.New("ths is a custom error")

error 本身是一个 interface,实现 interface 的方法即可定义我们自己的 error:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}

如何实现统一的错误处理逻辑?

现在我们想要实现一个统一的错误处理逻辑,上面的向文件中写内容的例子过于简单,现在我们试图实现一个文件显示服务器,它能够打开给定的 url 并将内容写入到文件当中:

package main

import (
"io"
"net/http"
"os"
)

func main() {
http.HandleFunc("/list/",
func(writer http.ResponseWriter, request *http.Request) {
path := request.URL.Path[len("/list/"):] // /list/fib.txt
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close()

all, err := io.ReadAll(file)
if err != nil {
panic(err)
}

writer.Write(all)
})

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

在浏览器打开 localhost:8888/list/fib.txt,即可显示:
在这里插入图片描述
但是如果给定一个错误的文件地址,程序仍然会 panic,我们希望对上面的错误 err 进行封装:

if err != nil {
http.Error(writer,
err.Error(),
http.StatusInternalServerError)
return
}

在这里插入图片描述
这种情况下,会将错误显示到浏览器,即直接展示给用户,这还不够好,我们希望对 err 的处理进行进一步的封装。

首先,我们对文件的目录进行组织。将服务器运行的代码放在 web.go 当中,将 web.go 放在 filelistingserver 目录下。在此基础上,在 filelistingserver 目录下新建 filelisting 目录,将 handler.go 放在 filelisting 目录下,handler 当中的内容是:

package filelisting

import (
"io"
"net/http"
"os"
)

func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
path := request.URL.Path[len("/list/"):] // /list/fib.txt
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()

all, err := io.ReadAll(file)
if err != nil {
return err
}

writer.Write(all)
return nil
}

👆 可以看到,它是对 http.HandleFunc(即最开始代码当中 main 函数体的第一行)第二个参数的抽象,在 HandleFileList 当中完成了服务的主要业务逻辑,但是在这个函数当中不对错误信息进行处理,而是统一地将错误信息进行返回,在其它地方对错误进行处理。

再来看目前的 web.go 文件的代码:

package main

import (
"learngo/errhandling/filelistingserver/filelisting"
"net/http"
"os"
)

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

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
if err != nil {
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
default:
code = http.StatusInternalServerError
}

http.Error(writer, http.StatusText(code), code)
}
}
}

func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))

err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}

web.go 当中新定义了一个名为 appHandler 的类型,它是func(writer http.ResponseWriter, request *http.Request) error的别名。而函数 errWrapper 试图对错误信息进行打包,它的输入就是一个函数,这个函数的别名正是 appHandler,它返回的是函数func(http.ResponseWriter, *http.Request),在它的返回值当中定义了一个匿名函数,这个匿名函数会对每一个错误类型进行详细的处理,并返回状态码,比如 StatusNotFound 对应的状态码是 404。通过对 web.go 进行错误包装处理,此时在 localhost:8888 错误的给定一个文件路径,将不会返回系统内部 panic 的错误,而是直接显示 Not Found,即:将错误进行封装,返回的错误信息是开发者希望用户看到的,而不是直接返回系统内部的错误信息。

errWrapper 是对函数式编程的典型应用,它的输入是一个函数,输出也是一个函数,相当于把输入的函数包装成了输出函数,听起来与 Python 的装饰器非常的相似。

error vs. panic

尽可能地不要使用 panic(意料之外的错误使用 panic,比如数组越界),意料之中的错误使用 error(比如:文件无法打开)。


原文地址:https://blog.csdn.net/Coffeemaker88/article/details/144304429

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!