golang笔记08--go语言错误处理和资源管理

golang笔记08--go语言错误处理和资源管理

  • 1 介绍
  • 2 错误处理和资源管理
    • 2.1 defer调用
    • 2.2 错误处理概念
    • 2.3 服务器统一出错处理
    • 2.4 panic和recover
    • 2.5 服务器统一出错处理2
  • 3 注意事项
  • 4 说明

1 介绍

本文继上文 golang笔记07–go语言函数式编程, 进一步了解 go语言的错误处理和资源管理,以及相应注意事项。
具体包括 : defer调用 、错误处理概念、服务器统一出错处理、panic和recover、服务器统一出错处理2 等内容。

2 错误处理和资源管理

2.1 defer调用

很多现代的编程语言中都有 defer 关键字,Go 语言的 defer 会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。

defer 调用:确保调用在函数结束时发生;

package main

import (
	"bufio"
	"fmt"
	"os"
)

func defer01() {
     
	for i := 0; i < 3; i++ {
     
		defer fmt.Println("defer: line ", i)
	}
	fmt.Println("defer: line  3")
}

func main() {
     
	fmt.Println("this is chapter 8.1")
	defer01()
}
输出:
this is chapter 8.1
defer: line  3
defer: line  2
defer: line  1
defer: line  0

2.2 错误处理概念

一般会对函数添加一些出错处理,知道的就按照自己直到的方式处理,不知道的类型就可以按照默认的方式处理,例如直接通过panic来处理。

本案例中,打开file.go 的 OpenFile 函数会发现, 如果存在 error 那么其类型为 *PathError;

package main

import (
	"bufio"
	"fmt"
	"os"
)

func writeFile(filename string) {
     
	fmt.Println("test writeFile")
	file, err := os.OpenFile(filename, os.O_EXCL|os.O_RDWR|os.O_CREATE, 0666)
	// err = errors.New("this is an unknown err")
	if err != nil {
     
		if pathErr, ok := err.(*os.PathError); !ok {
     
			panic(err)
		} else {
     
			fmt.Printf("%s, %s, %s\n", pathErr.Op, pathErr.Path, pathErr.Err)
		}
		return
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	defer writer.Flush()
	for i := 0; i < 20; i++ {
     
		fmt.Fprintln(writer, i)
	}
}

func main() {
     
	fmt.Println("this is chapter 8.2")
	writeFile("8.2.txt")
}
输出:
当8.2.txt存在的时候,再次创建就会报错:
this is chapter 8.2
test writeFile
open, 8.2.txt, file exists
当新建一个 errors.New 后,就会出现不知道的 error类型,从而触发panic

2.3 服务器统一出错处理

本案例使用 http 来读取文件,并设置了多种不同的出错处理方式;
案例中的 http 如果出错了,并不会直接退出, 其原因为其内部使用了recover;

vim filelisting/hander.go
package filelisting

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

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

vim web.go 
package main

import (
	"learngo/chapter8/8.3/filelisting"
	"net/http"
	"os"

	"github.com/gpmgo/gopm/log"
)

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

func errWarpper(handler appHandler) func(w http.ResponseWriter, r *http.Request) {
     
	return func(writer http.ResponseWriter,
		request *http.Request) {
     
		err := handler(writer, request)
		if err != nil {
     
			log.Warn("error occurred handling request: %s", err.Error())
			code := http.StatusOK
			switch {
     
			case os.IsNotExist(err):
				code = http.StatusNotFound
				//http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}
			http.Error(writer, http.StatusText(code), code)
		}
	}
}

func main() {
     
	http.HandleFunc("/list/", errWarpper(filelisting.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
     
		panic(err)
	}
}
输出:
gopm WARN error occurred handling request: open 8.1.txt1: no such file or directory
gopm WARN error occurred handling request: open 8.2.txt: permission denied

结果1(直接ip:8888 ):
http://127.0.0.1:8888/
404 page not found
结果2(8.1.txt 存在,直接输出结果):
http://127.0.0.1:8888/list/8.1.txt
0
1
......
18
19
结果3(8.1.txt1 不存在,直接输出结果):
http://127.0.0.1:8888/list/8.1.txt1
Not Found
结果4(8.2.txt 为root权限):
http://127.0.0.1:8888/list/8.2.txt
Forbidden

2.4 panic和recover

panic(尽量少用):
停止当前函数执行;
一直向上返回,执行每一层的defer;
如果没有遇见recover,程序退出;
recover:
仅在defer调用中使用;
获取 panic 的值;
如果无法处理,可以重新 panic;

package main

import (
	"errors"
	"fmt"
)

func myRecover() {
     
	r := recover()
	if r == nil {
     
		fmt.Print("nothing to recover")
		return
	}
	if err, ok := r.(error); ok {
     
		fmt.Println("error occurred:", err)
	} else {
     
		panic(fmt.Sprintf("I don't know what to do $v", r))
	}
}
func tryRecover() {
     
	//defer func() {此处也可以直接将上述函数中的内放在一个匿名函数中}()
	defer myRecover()
	panic(errors.New("this is an error"))
}

func tryRecover2() {
     
	defer myRecover()
	b := 0
	a := 5 / b
	fmt.Print(a)
}
func tryRecover3() {
     
	defer myRecover()
	panic(123)
}
func main() {
     
	fmt.Println("this is chapter 8.4")
	tryRecover()
	tryRecover2()
	tryRecover3()
}
输出:
this is chapter 8.4
error occurred: this is an error
error occurred: runtime error: integer divide by zero
panic: 123 [recovered]
        panic: I don't know what to do $v%!(EXTRA int=123)

goroutine 1 [running]:
......
Process finished with exit code 2

2.5 服务器统一出错处理2

error vs panic:
意料之中的使用error,如文件打不开;
意料之外的使用panic,如数组越界。

本案例中结合如下三种方式来实现一个优雅的错处处理应用示例:
defer + panic + recover;
Type Assertion;
函数式编程的应用。

以下案例在2.3 的基础上进一步强化,结合Type Assertion 来判断断err类型,对于异常的前缀,把指定的err信息输出给用户。

vim filelisting/hander.go
package filelisting

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const prefix = "/list/"

type userError string

func (e userError) Error() string {
     
	return e.Message()
}

func (e userError) Message() string {
     
	return string(e)
}

func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
     
	if strings.Index(request.URL.Path, prefix) != 0 {
     
		return userError(fmt.Sprintf("path %s must start with %s", request.URL.Path, prefix))
	}
	path := request.URL.Path[len("/list/"):]
	file, err := os.Open(path)
	if err != nil {
     
		// panic(err)
		// http.Error(writer, err.Error(), http.StatusInternalServerError)
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)
	if err != nil {
     
		return err
	}
	writer.Write(all)
	return nil
}

vim web.go
package main

import (
	"learngo/chapter8/8.3/filelisting"
	"net/http"
	"os"

	"github.com/gpmgo/gopm/log"
)

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

func errWarpper(handler appHandler) func(w http.ResponseWriter, r *http.Request) {
     
	return func(writer http.ResponseWriter, request *http.Request) {
     
		defer func() {
     
			if r := recover(); r != nil {
     
				log.Log("Panic: %v", r)
				http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
			}
		}()

		err := handler(writer, request)
		if err != nil {
     
			log.Warn("error occurred handling request: %s", err.Error())
			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
				//http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}
			http.Error(writer, http.StatusText(code), code)
		}
	}
}

// 自定义用户 error接口
type userError interface {
     
	error
	Message() string
}

func main() {
     
	http.HandleFunc("/", errWarpper(filelisting.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
     
		panic(err)
	}
}
输出:
gopm WARN error occurred handling request: path /li must start with /list/
结果1:
http://127.0.0.1:8888/li
path /li must start with /list/

3 注意事项

  1. 案例中使用 log.Warn, 其来源为 gpmgo/gopm/log
    go get -v github.com/gpmgo/gopm
    

4 说明

  1. 软件环境
    go版本:go1.15.8
    操作系统:Ubuntu 20.04 Desktop
    Idea:2020.01.04
  2. 参考文档
    5.3 defer --go语言设计与实现
    由浅入深掌握Go语言 --慕课网
    go 语言编程 --许式伟

你可能感兴趣的:(Golang,golang,go,错误处理,go,defer,go,panic,go,recover)