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

时间:2022-09-04 22:29:44

一、defer

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

2. defer列表为先进后出

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

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

  1. package main
  2.  
  3. import (
  4. "aaa/functional/fbi"
  5. "bufio"
  6. "fmt"
  7. "os"
  8. )
  9.  
  10. // 我要写文件
  11. func writeFile() {
  12. file, err := os.Create("test.txt")
  13. if err != nil {
  14. panic("error")
  15. }
  16. defer file.Close()
  17.  
  18. w := bufio.NewWriter(file)
  19. defer w.Flush()
  20.  
  21. f := fbi.Feibonaccq()
  22. for i := 0; i < 20; i++ {
  23. fmt.Fprintln(w, f())
  24. }
  25.  
  26. }
  27. func main() {
  28. writeFile()
  29. }
  1. package fbi
  2.  
  3. func Feibonaccq() func() int {
  4. x, y := 0, 1
  5. return func() int {
  6. x, y = y, x+y
  7. return x
  8. }
  9. }

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

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

  1. func tryDefer() {
  2. for i := 0; i < 10 ; i++ {
  3. defer fmt.Println(i)
  4. }
  5. }

打印结果:

9

8

7

6

5

4

3

2

1

0

二、错误处理

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

比如下面的操作:

  1. package main
  2.  
  3. import (
  4. "aaa/functional/fbi"
  5. "bufio"
  6. "fmt"
  7. "os"
  8. )
  9.  
  10. // 我要写文件
  11. func writeFile(filename string) {
  12. // os.O_EXCL|os.O_CREATE创建一个新文件, 并且他必须不存在
  13. file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
  14. // 这时候打印panic就不太友好. 我们可以对错误类型进行处理
  15. /*if err != nil {
  16. panic("error")
  17. }*/
  18.  
  19. // 这里就对错误的类型进行了捕获处理.
  20. if err, ok := err.(*os.PathError); !ok {
  21. fmt.Println("未知错误")
  22. } else {
  23. fmt.Printf("%s, %s, %s", err.Path, err.Op, err.Err)
  24. }
  25.  
  26. defer file.Close()
  27.  
  28. w := bufio.NewWriter(file)
  29. defer w.Flush()
  30.  
  31. f := fbi.Feibonaccq()
  32. for i := 0; i < 20; i++ {
  33. fmt.Fprintln(w, f())
  34. }
  35.  
  36. }
  37. func main() {
  38. writeFile("test.txt")
  39. }

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

三、统一错误处理的逻辑

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

  1. package main
  2.  
  3. import (
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. )
  8.  
  9. // 我们来模拟一个web服务器. 在url上输入一个地址, 然后显示文件内容
  10. // 做一个显示文件的web server
  11. func main() {
  12. http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
  13. // 获取url路径, 路径是/list/之后的部分
  14. path := request.URL.Path[len("/list/"):]
  15. // 打开文件
  16. file, err := os.Open(path)
  17. if err != nil {
  18. panic("err")
  19. }
  20. defer file.Close()
  21.  
  22. // 读出文件
  23. b, err := ioutil.ReadAll(file)
  24. if err != nil {
  25. panic("err")
  26. }
  27.  
  28. // 写入文件到页面
  29. writer.Write(b)
  30. })
  31.  
  32. // 监听端口:8888
  33. err := http.ListenAndServe(":8888", nil)
  34. if err != nil {
  35. panic("err")
  36. }
  37. }

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

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

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

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

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

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

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

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

于是做了如下修改.

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

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

  1. package fileListener
  2.  
  3. import (
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. )
  8.  
  9. func FileHandler(writer http.ResponseWriter, request *http.Request) error{
  10. // 获取url路径, 路径是/list/之后的部分
  11. path := request.URL.Path[len("/list/"):]
  12. // 打开文件
  13. file, err := os.Open(path)
  14. if err != nil {
  15. return err
  16. }
  17. defer file.Close()
  18.  
  19. // 读出文件
  20. b, err := ioutil.ReadAll(file)
  21. if err != nil {
  22. return err
  23. }
  24.  
  25. // 写入文件到页面
  26. writer.Write(b)
  27. return nil
  28. }

第二: 封装错误内容

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

  1. // 定义一个函数类型的结构, 返回值是erro
  2. type Handler func(writer http.ResponseWriter, request *http.Request) error
  3.  
  4. // 封装error
  5. func WrapHandler(handler Handler) func (http.ResponseWriter, *http.Request) {
  6. return func(writer http.ResponseWriter, request *http.Request) {
  7. // 执行原来的逻辑. 然后增加error的错误处理
  8. err := handler(writer, request)
  9. if err != nil {
  10. code := http.StatusOK
  11. switch {
  12. case os.IsNotExist(err):
  13. code = http.StatusNotFound
  14.  
  15. case os.IsPermission(err):
  16. code = http.StatusServiceUnavailable
  17. default:
  18. code = http.StatusInternalServerError
  19. }
  20. http.Error(writer, http.StatusText(code), code)
  21. }
  22. }
  23. }

调用的部分

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

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

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

四、panic

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

1. 停止当前函数的执行

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

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

五、recover

1. 在defer 中调用

2. 获取panic的值

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

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "github.com/pkg/errors"
  6. )
  7.  
  8. func tryRecover() {
  9.  
  10. defer func(){
  11. r := recover()
  12. if r, ok := r.(error); ok {
  13. fmt.Println("error 发生", r.Error())
  14. } else {
  15. panic(fmt.Sprintf("未知错误:%v", r))
  16. }
  17. }()
  18. panic(errors.New("错误"))
  19.  
  20. }
  21.  
  22. func main() {
  23. tryRecover()
  24. }

六、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

  1. // 封装error
  2. func WrapError(handler Handler) func (http.ResponseWriter, *http.Request) {
  3. return func(writer http.ResponseWriter, request *http.Request) {
  4. defer func(){
  5. if r := recover(); r != nil {
  6. fmt.Println("发生错误")
  7. http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
  8. }
  9. }()
  10.  
  11. // 执行原来的逻辑. 然后增加error的错误处理
  12. err := handler(writer, request)
  13. if err != nil {
  14. code := http.StatusOK
  15. switch {
  16. case os.IsNotExist(err):
  17. code = http.StatusNotFound
  18.  
  19. case os.IsPermission(err):
  20. code = http.StatusServiceUnavailable
  21. default:
  22. code = http.StatusInternalServerError
  23. }
  24. http.Error(writer, http.StatusText(code), code)
  25. }
  26. }
  27. }

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

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

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

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

  1. func FileHandler(writer http.ResponseWriter, request *http.Request) error {
  2. // 获取url路径, 路径是/list/之后的部分
  3. if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
  4. return errors.New("url 不是已list开头")
  5. }
  6. path := request.URL.Path[len("/list/"):]
  7. // 打开文件
  8. file, err := os.Open(path)
  9. if err != nil {
  10. return err
  11. }
  12. defer file.Close()
  13.  
  14. // 读出文件
  15. b, err := ioutil.ReadAll(file)
  16. if err != nil {
  17. return err
  18. }
  19.  
  20. // 写入文件到页面
  21. writer.Write(b)
  22. return nil
  23. }

页面打印效果

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

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

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

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

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

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

  1. // 执行原来的逻辑. 然后增加error的错误处理
  2. err := handler(writer, request)
  3. if err != nil {
  4. if userErr, ok := err.(userError); ok {
  5. http.Error(writer, userErr.Message(), http.StatusBadRequest)
  6. return
  7. }
  8. code := http.StatusOK
  9. switch {
  10. case os.IsNotExist(err):
  11. code = http.StatusNotFound
  12.  
  13. case os.IsPermission(err):
  14. code = http.StatusServiceUnavailable
  15. default:
  16. code = http.StatusInternalServerError
  17. }
  18. http.Error(writer, http.StatusText(code), code)
  19. }

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

  1. type userError string
  2.  
  3. func (u userError) Error() string{
  4. return u.Message()
  5. }
  6.  
  7. func (u userError) Message() string {
  8. return string(u)
  9. }
  1. func FileHandler(writer http.ResponseWriter, request *http.Request) error {
  2. // 获取url路径, 路径是/list/之后的部分
  3. if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
  4. return userError("url 不是已list开头")
  5. }
  6. path := request.URL.Path[len("/list/"):]
  7. // 打开文件
  8. file, err := os.Open(path)
  9. if err != nil {
  10. return err
  11. }
  12. defer file.Close()
  13.  
  14. // 读出文件
  15. b, err := ioutil.ReadAll(file)
  16. if err != nil {
  17. return err
  18. }
  19.  
  20. // 写入文件到页面
  21. writer.Write(b)
  22. return nil
  23. }

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

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

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

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

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

4. 打印自定义异常.

以下是完整代码

  1. package handling
  2.  
  3. import (
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. "strings"
  8. )
  9.  
  10. type UserError struct {
  11. Content string
  12. }
  13.  
  14. func (u UserError) Error() string {
  15. return u.Message()
  16. }
  17.  
  18. func (u UserError) Message() string {
  19. return u.Content
  20. }
  21.  
  22. func Hanldering(writer http.ResponseWriter, request *http.Request) error {
  23. // 获取url, list之后的就是url
  24. if s := strings.Index(request.URL.Path, "/list/"); s != 0 {
  25. return UserError{"path error, /list/"}
  26. }
  27. url := request.URL.Path[len("/list/"):]
  28.  
  29. // 根据url打开文件
  30. file, err := os.Open(url)
  31. if err != nil {
  32. return os.ErrNotExist
  33. }
  34. defer file.Close()
  35.  
  36. // 打开以后把文件内容读出来
  37. f, err := ioutil.ReadAll(file)
  38. if err != nil {
  39. return os.ErrPermission
  40. }
  41.  
  42. // 读出来以后, 写入到页面
  43. writer.Write(f)
  44. return nil
  45. }
  1. package main
  2.  
  3. import (
  4. "aaa/handlerError/linstenerFile/handling"
  5. "github.com/siddontang/go/log"
  6. "net/http"
  7. "os"
  8. )
  9.  
  10. type ErrorHandlering func(writer http.ResponseWriter, request *http.Request) error
  11.  
  12. func WrapError(handler ErrorHandlering) func(http.ResponseWriter, *http.Request) {
  13. return func(writer http.ResponseWriter, request *http.Request) {
  14. defer func() {
  15. if r := recover(); r != nil {
  16. log.Warn("other error")
  17. http.Error(writer, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
  18. }
  19. }()
  20.  
  21. err := handler(writer, request)
  22.  
  23. //自定义异常处理
  24.  
  25. // 错误处理
  26. if err != nil {
  27. if userErr, ok := err.(UserError); ok {
  28. log.Warn("user error:", userErr.Message())
  29. http.Error(writer, userErr.Message(), http.StatusBadRequest)
  30. return
  31. }
  32. code := http.StatusOK
  33. switch err {
  34. case os.ErrNotExist:
  35. code = http.StatusNotFound
  36. case os.ErrPermission:
  37. code = http.StatusBadRequest
  38. default:
  39. code = http.StatusInternalServerError
  40. }
  41. http.Error(writer, http.StatusText(code), code)
  42.  
  43. }
  44. }
  45. }
  46.  
  47. type UserError interface {
  48. error
  49. Message() string
  50. }
  51.  
  52. func main() {
  53. // 模拟web请求
  54. http.HandleFunc("/", WrapError(handling.Hanldering))
  55.  
  56. // 指定服务端口
  57. http.ListenAndServe(":8888", nil)
  58. }

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

原文链接:https://www.cnblogs.com/ITPower/p/12310539.html