天天看点

GO资源管理和错误处理

GO资源管理和错误处理

    • 资源管理 defer
    • 错误处理
      • panic
      • recover
    • 统一得错误处理逻辑

资源管理 defer

程序中的资源管理通常是指一些成对出现的操作,例如:文件的打开和关闭,数据库的连接和释放,这通常不是一件复杂的事情,但是程序可能出错从中间跳出来,导致资源得不到释放,最终会严重地影响系统的整体性能。GO语言是通过defer调用来实现资源管理的。Go垃圾回收(gc)是内存垃圾回收,分配给对象的内存回收,对于资源必须手动释放还给操作系统。

  • 确保调用在函数结束的时候发生
  • defer内部相当于一个栈,defer列表为先进后出
  • 参数在defer语句中是进行计算的,只是没有马上调用
  • 使用defer调用的场景:

    Open / Close

    Lock / Unlock

    PrintHeader / PrintFooter

例子:defer在函数结束的时候发生

package main
import "fmt"
func main(){
	defer fmt.Println(1) //放在栈中函数结束的时候再执行
	fmt.Println(2)
}
//输出结果:2  1
           

例子:defer列表先进后出

package main
import "fmt"
func main(){
	//defer 在函数结束的时候执行 包括函数主动return或程序出错了
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
	//return
	panic("好像出错了...")
	fmt.Println(4)
}
//输出结果:3   2  1  panic: 好像出错了...
           

例子:参数在defer语句中计算

package main
import "fmt"
func main(){
	for i:=0 ; i<6 ; i++{
		defer fmt.Println(i)
	}
}
//输出结果:5 4 3 2 1 0   不是一直都是 5
           

文件资源管理

package main
import (
	"bufio"
	"ccmouse/defer/fibonacci"
	"fmt"
	"os"
)
func writeFile(){
	//创建一个文件 ---关闭文件资源
	file,err := os.Create("fibonacci.txt")
	if err != nil {
		panic(err)
	}
	//关闭文件资源
	defer file.Close()
	f := fibonacci.Fibonacci()
	//使用缓存写入内存中比较快----最后写入文件中
	witer := bufio.NewWriter(file)
	for i:=1;i<21 ;i++  {
		fmt.Fprintln(witer,f())
	}
	//最后写入文件中
	defer witer.Flush()
}
func main(){
	writeFile()
}
           
package fibonacci
func Fibonacci() func()int{
	a,b := 0,1
	return func()int{
		a,b = b,a+b
		return a
	}
}
           

错误处理

GO语言的处理通常了 panic、defer、recover结合使用的。GO中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

panic

  • 内建函数
  • 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
  • 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally
  • 直到goroutine整个退出,并报告错误
  • 如果没有遇见 recover, 程序退出
file,err := os.OpenFile("fibonacci.txt",os.O_EXCL|os.O_CREATE,0666)
	//自己创建一个错误
	err1 := errors.New("this is custom error")
	fmt.Println(err1)
	//If there is an error, it will be of type *PathError.
	if err != nil {
		if pathErr,ok := err.(*os.PathError);!ok{ //err接口类型的断言
			//抛出异常
			panic(err)
		}else {
			//Op 操作 Path路径 Err错误信息
			log.Println(pathErr.Op,pathErr.Path,pathErr.Err)
		}
		fmt.Println("errror:",err.Error())
		return
		//panic(err)
	}
           

recover

  • 内建函数
  • 仅在 defer 调用中使用,如果无法处理,可重新 Panic
  • 用来控制一个goRoutine的panicking行为,捕获panic,从而影响应用的行为
  • 调用建议:在defer函数中,通过recover来终止一个goRoutine的panicking过程,从而恢复正常代码的执行;可以获取通过panic传递的error

    简单来讲:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

package main
import "fmt"
func myFuc(){
	fmt.Println("c")
	panic("this is custom error")
	fmt.Println("d")
}
func main(){
	fmt.Println("a")
	//先定义defer 不然捕获不到panic
	defer func(){
		if err := recover() ; err != nil { //recover捕获错误信息 并且处理
			fmt.Println(err)
		}
	}()
	myFuc()
	fmt.Println("b")
}
//输出信息:a c this is custom error
           

统一得错误处理逻辑

  • 业务处理逻辑封装到函数中,遇见错误就return,返回错误error
  • 封装一个错误处理的函数,分级别处理错误
package main
import (
	"ccmouse/errhandle/handle"
	"github.com/astaxie/beego/logs"
	"net/http"
	"os"
)
//处理业务逻辑的函数类型
type AppHandler func(w http.ResponseWriter,r *http.Request) error
//处理返回错误的函数 
//函数式编程输入是函数输出是函数
func ErrWrapper(ah AppHandler) func(w http.ResponseWriter,r *http.Request){
	//返回的是Http.HandleFunc 需要的函数参数
	return func(w http.ResponseWriter, r *http.Request) {
		//ah处理的是逻辑业务
		err := ah(w,r)
		//-----以下是处理错误的程序-------
		if err != nil { //很多不同的错误 需要区别处理
			code := http.StatusOK
			//记录错误日志
			logs.Warning("handling request err: %s\n",err.Error())
			switch  {
			//文件不存在的错误
			case os.IsNotExist(err):
				code = http.StatusNotFound
			//文件没有权限
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}
			http.Error(w,http.StatusText(code),code)
		}
	}
}
func main(){
	http.HandleFunc("/list/", ErrWrapper(handle.FileList))
	http.ListenAndServe(":8888",nil)
}
           
package handle
import (
	"io/ioutil"
	"net/http"
	"os"
)
func FileList(w http.ResponseWriter,r *http.Request) error{
	path := r.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
	}
	w.Write(all)
	return nil
}
           
  • 处理用户自定义的错误类型
package main
import (
	"ccmouse/errhandle/handle"
	"github.com/astaxie/beego/logs"
	"github.com/gpmgo/gopm/modules/log"
	"net/http"
	"os"
)
type AppHandler func(w http.ResponseWriter,r *http.Request) error
//区分能给用户看的错误信息和不能给用户看的错误信息
//定义可以给用户看到的错误类型
type UserError interface {
	error
	Message() string
}
//处理返回错误的函数
func ErrWrapper(ah AppHandler) func(w http.ResponseWriter,r *http.Request){
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			err1 :=recover()
			if err1 != nil {
				log.Warn("sever err: %v\n",err1)
				http.Error(w,http.StatusText(http.StatusInternalServerError),http.StatusInternalServerError)
			}
		}()
		err := ah(w,r)
		if err != nil { //很多不同的错误 需要区别处理
			//用户可以看见的错误信息单独处理
			if userErr,ok := err.(UserError);ok{
				http.Error(w,userErr.Error(),http.StatusBadRequest)
				return
			}
			//以下是系统自带的错误信息
			code := http.StatusOK
			//记录错误日志
			logs.Warning("handling request err: %s\n",err.Error())
			switch  {
			//文件不存在的错误
			case os.IsNotExist(err):
				code = http.StatusNotFound
			//文件没有权限
			case os.IsPermission(err):
				code = http.StatusForbidden
			default:
				code = http.StatusInternalServerError
			}
			http.Error(w,http.StatusText(code),code)
		}
	}
}
func main(){
	http.HandleFunc("/", ErrWrapper(handle.FileList))
	http.ListenAndServe(":8888",nil)
}
           
package handle
import (
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)
//定义用户能看见的error
type UserError string
func (e UserError) Error() string{
	return e.Message()
}
func (e UserError)Message() string{
	return string(e)
}
const PREFIX  = "/list/"
func FileList(w http.ResponseWriter,r *http.Request) error{
	//判断path里面有没有/list/
	index := strings.Index(r.URL.Path,PREFIX)
	if index < 0 {
		//自定义用户信息
		return UserError("path must start with '/list/'")
	}
	path := r.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
	}
	w.Write(all)
	return nil
}