当前位置: 首页 > article >正文

【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(比如:文件无法打开)。


http://www.kler.cn/a/428639.html

相关文章:

  • 基于 Spring Boot 和 Vue.js 的全栈购物平台开发实践
  • 管道符、重定向与环境变量
  • 兼职全职招聘系统架构与功能分析
  • 图的基本概念
  • 无人机飞手考证难度增加,实操、地面站教学技术详解
  • 记一次常规的网络安全渗透测试
  • Linux下mysql环境的搭建
  • Unity中使用Sqlite存储本地数据
  • Python之爬虫入门(1)
  • 电商项目-微服务网关使用的问题
  • 透彻理解并解决Mockito模拟框架的单元测试无法运行的问题
  • redis击穿,穿透,雪崩以及解决方案
  • unity 2D像素种田游戏学习记录(自用)
  • Three.js曲线篇 6.雕刻平面大师shape
  • Java IO流与NIO技术综合应用
  • Qt实现自定义消失动画弹出提示框
  • 数据结构 (27)查找的基本概念
  • 基于MATLAB野外观测站生态气象数据处理分析实践应用
  • HCIA-openGauss_2_2连接与认证
  • 2024第十六届蓝桥杯模拟赛(第二期)-Python
  • 汽车总线协议分析-CAN-FD总线
  • ORB-SLAM2 ---- 词袋模型BOW
  • PHP语法学习(第七天)-循环语句,魔术常量
  • quartz 架构详解
  • 算法-字符串-43.字符串相乘
  • 【并集查询】.NET开源 ORM 框架 SqlSugar 系列