10、Golang 教程 - 错误处理和资源管理

defer调用

  • 确保调用在函数结束时执行
  • 参数在调用defer语句时计算,然后押入栈中
func tryDefer() {
   
     
	for i := 0; i < 30; i++ {
   
     
		defer fmt.Println(i) 
		//29
		//28
		//27
		//26
		//....
		//1
		//0
	}
}

  • defer列表是一个栈结构,后进先出,
func tryDefer() {
   
     
	defer fmt.Println(3)
	defer fmt.Println(2)
	fmt.Println(1)
	//1
	//2
	//3

}

//...

func tryDefer() {
   
     
	defer fmt.Println(3)
	defer fmt.Println(2)
	fmt.Println(1)
	panic("error")
	fmt.Println(5)
	//1
	//2
	//3
	//panic: error

}

  • defer的调用的一般情况

  • Open\Close

  • Lock\Unlock

  • PrintHeader\PrintFooter

func writeFile(filename string) {
   
     
	file, err := os.Create(filename)
	if err != nil {
   
     
		panic("error")
	}
	defer file.Close()  // 关闭文件

	writer := bufio.NewWriter(file)
	defer writer.Flush() // 把缓存的数据刷到文件中去

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

}
func main() {
   
     
	writeFile("fib.txt")
}

错误处理

type error interface {
   
     
	Error() string
}
//....

func writeFile(filename string) {
   
     
	file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
	//err = errors.New("this is a custom error") // 可以自己定义一个error
	if err != nil {
   
     
		//fmt.Println("error",err.Error()) //error open fib.txt: file exists
		//return

		if pathError, ok := err.(*os.PathError); !ok {
   
      //判断err是否为指定类型
			panic(err)
		} else {
   
     
			fmt.Println(pathError.Op,  //open
				pathError.Path, //fib.txt
				pathError.Err) //file exists
		}
		return
	}
	defer file.Close()

	writer := bufio.NewWriter(file)
	defer writer.Flush() // 把缓存的数据刷到文件中去

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

}

func main() {
   
     
	writeFile("fib.txt")
}

补充:类型断言

  <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言
  <目标类型的值> := <表达式>.( 目标类型 )  //非安全类型断言

类型断言的本质,跟类型转换类似,都是类型之间进行转换,不同之处在于,类型断言是在接口之间进行,相当于在Java中,对于一个对象,把一种接口的引用转换成另一种。

func test6() {
   
     
    var i interface{
   
     } = "kk"
    j := i.(int) //把i转换成int类型 系统内部检测到不匹配 会调用panic 抛出异常
    fmt.Printf("%T->%d\n", j, j)
}
func test6() {
   
     
    var i interface{
   
     } = "TT"
    j, ok := i.(int)
    if ok {
   
      //安全类型的断言
        fmt.Printf("%T->%d\n", j, j)
    } else {
   
     
        fmt.Println("类型不匹配")
    }
}

服务器错误统一处理


func main() {
   
     
	http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
   
     
		path := request.URL.Path[len("/list/"):]
		file, err := os.Open(path)
		if err != nil {
   
     
			//访问 : http://localhost:8888/list/fib.txta
			//open fib.txta: no such file or directory
			// 这样会把我们服务器内部错误暴露给客户端,这样做不好
			// 而且这个本来是一个业务逻辑,不要把错误处理和业务逻辑耦合在一起,把业务逻辑和错误处理分开
			http.Error(writer,err.Error(),http.StatusInternalServerError)
			return
		}
		defer file.Close()

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

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

  • 错误统一处理
package main

import (
	"learn2/learnerror/server"
	"net/http"
	"os"
)

//  定义一下这个函数类型
type appHandle func(writer http.ResponseWriter, request *http.Request) error

// 错误统一处理 函数式编程,入参是一个函数,出参也是一个函数
func errorWrapper(handle appHandle) func(http.ResponseWriter, *http.Request) {
   
     
	return func(writer http.ResponseWriter, request *http.Request) {
   
     
		err := handle(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() {
   
     
	// 用errorWrapper包装一下这个业务逻辑函数
	http.HandleFunc("/list/", errorWrapper(server.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
   
     
		panic(err)
	}
}
// 业务逻辑
package server

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 {
   
     
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)
	if err != nil {
   
     
		return err
	}
	writer.Write(all)
	return nil // 没有错误返回nil
}

panic 和 recover

panic

  • 不要经常用panic,和其他语言的throw差不多
  • 停止当前函数的执行
  • 一直向上返回,执行每一层的defer
  • 如果没有遇见recover,程序退出

recover

  • 仅在defer调用中使用
  • 获取panic的值
  • 如果无法处理,可以重新panic
func tryRecover(){
   
     
	defer func(){
   
      // func(){} 只是一个函数体, 要运行这个函数要在后面加上()
		r := recover() //recover只能在defer中调用
		if r == nil {
   
      // 如果没有异常  r为nil
			return
		}
		if error, ok := r.(error); ok {
   
     
			fmt.Println(error.Error())
		}else {
   
     
			panic(r) //不知道怎么处理 可以继续panic
		}
	}()
	panic(errors.New("error"))
	// panic(124)//这个因为不是错误 defer中会继续抛出
}
func main() {
   
     
		tryRecover()
}

服务器错误统一处理2

package main

import (
	"learn2/learnerror/server"
	"net/http"
	"os"
)

//  定义一下这个函数类型
type appHandle func(writer http.ResponseWriter, request *http.Request) error

// 错误统一处理 函数式编程,入参是一个函数,出参也是一个函数
func errorWrapper(handle appHandle) func(http.ResponseWriter, *http.Request) {
   
     
	return func(writer http.ResponseWriter, request *http.Request) {
   
     
		//虽然说http里面发生错误会自己recover 但是我们还是自己做下处理比较好
		defer func() {
   
      // 匿名函数的写法
			r := recover()
			if r != nil {
   
      
				http.Error(writer,http.StatusText(http.StatusInternalServerError),http.StatusInternalServerError)
				return
			}
		}()
		err := handle(writer, request)
		if err != nil {
   
      // 错误统一处理
			// 我们自己定义的错误,有一些要返回给用户,可以这样做
			if userError,ok:= err.(userError);ok{
   
       //判断err是否属于某个接口类型
				 http.Error(writer,userError.Error(),http.StatusBadRequest)
				 return
			}
			
			code := http.StatusOK
			switch {
   
     
			case os.IsNotExist(err):
				code = http.StatusNotFound

			default:
				code = http.StatusInternalServerError
			}
			http.Error(writer, http.StatusText(code), code)
		}

	}
}
// 用户可以看到的error类型
// 接口
type userError interface {
   
     
	error
	Message() string
}

func main() {
   
     
	// 用errorWrapper包装一下这个业务逻辑函数
	// 让 / 下的网页都可以访问,
	http.HandleFunc("/", errorWrapper(server.HandleFileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {
   
     
		panic(err)
	}
}
//
package server

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

type userError string

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

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

// 把业务逻辑分离开来,如果有错误,就返回一个错误,
const prefix = "/list/"
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
   
     
	if strings.Index(request.URL.Path, prefix) != 0 {
   
     // 如果不是以list开头,我们自己抛出一个自己定义的代码
		return userError("path must start with" + prefix) //强转
	}
	path := request.URL.Path[len(prefix):] // 这里可能会切片下标越界
	file, err := os.Open(path)
	if err != nil {
   
     
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)
	if err != nil {
   
     
		return err
	}
	writer.Write(all)
	return nil // 没有错误返回nil
}

error vs panic

  • 意料之中使用error

  • error是用于防止错误情况的问题,用error进行错误提示

  • 当有的错误属于异常情况下,正常代码运行不会出现的问题的情况就需要用panic了,但是也需要进行panic的recover处理

  • 意料之外使用panic

  • 尽量不要使用panic