关于golang的错误处理

延迟返回关键字 defer 作用是在程序结束或异常之前执行

package main

import "fmt"

func tryDefer(){
	defer fmt.Println("1")
	defer fmt.Println("2")
	fmt.Println("3")
	return
	fmt.Println("4")
}

func main(){
	tryDefer()
}//返回值为  3 2 1 由于打印4之前程序结束 故4不打印

defer调用

.确保调用在函数结束时发生

.参数在defer语句时计算

.defer列表为先进后出

一般在何时使用defer

.Open/Clos

.Lock/Unlock

.PrintHeader/PrintFooter

下面贴一个defer的使用方法

package main

import (
	"fmt"
	"os"
	"bufio"
	"awesomeProject1/test/fib/fibs"
)

func tryDefer(){
	defer fmt.Println("1")
	defer fmt.Println("2")
	fmt.Println("3")
	return
	fmt.Println("4")
}
func writeFile(filename string){
	filer,err:=os.Create(filename)  //尝试创建filename文件
	if err != nil {
		panic(err)
	}
	defer filer.Close()				//程序结束时关闭filer

	writer :=bufio.NewWriter(filer)	//文件写入变量writer
	defer writer.Flush()			//程序结束时将缓存内容传输到文件
	f := fibs.Fibonacco()				//函数即类型 f替代fibs.fibonacco进行运算

	for i:= 0; i< 20; i++{
		fmt.Fprintln(writer,f())		//将函数返回值放入writer缓存中 程序结束 执行defer采用先进后出顺序
	}
}

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

上面演示的是defer的实际应用例子 下面我们优化一下 让err在我们意料之中报错

package main

import (
	"fmt"
	"os"
	"bufio"
	"awesomeProject1/test/fib/fibs"
)

func tryDefer(){
	defer fmt.Println("1")
	defer fmt.Println("2")
	fmt.Println("3")
	return
	fmt.Println("4")
}
func writeFile(filename string){
    //os.OpenFile返回两个值 err实际是string类型
	filer,err:=os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)  
	if err != nil {						//判断err是否有内容 有则出错
	if patherror,ok := err.(*os.PathError); !ok{//判断err出错内容是否os.patherror已知错误
			panic(err)					//不是则打印异常
		}else{
			fmt.Printf("%s, %s, %s\n",					//反之打印出已知错误
				patherror.Op,							//有返回值带回来的内容全部打印
				patherror.Path,
				patherror.Err)
		}
	}
	defer filer.Close()				//程序结束时关闭filer

	writer :=bufio.NewWriter(filer)	//文件写入变量writer
	defer writer.Flush()			//程序结束时将缓存内容传输到文件
	f := fibs.Fibonacco()				//函数即类型 f替代fibs.fibonacco进行运算

	for i:= 0; i< 20; i++{
		fmt.Fprintln(writer,f())//将函数返回值放入writer缓存中 程序结束 执行defer采用先进后出顺序
	}
}

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

defer先说到这 只要明白在什么地方用defer就可以了 大部分情况是错误或结束时运行

下面我们在看一个稍微复杂一点的例子

例子中的程序是在网页显示指定文件夹内的文件 

package main

import (
	"net/http"
	"os"
	"io/ioutil"
)
func main() {
	http.HandleFunc("/list/",func (writer http.ResponseWriter,request *http.Request) { //函数式编程特点 函数做参数
		path := request.URL.Path[len("/list/"):] //在网页内输入指定符号 /list/ 后跟文件名 找到程序.exe文件所在同级目录及子目录下文件
		file, err := os.Open(path)					//打开文件是否成功 成功则返回文件file
		if err != nil {
			panic(err)
		}
		defer file.Close()
		all, err := ioutil.ReadAll(file)			// 读出文件内所有内容并放入all中
		if err != nil {
			panic(err)
		}
		writer.Write(all)							//将内容打印到指定网页内
	})
	http.ListenAndServe(":8888",nil) //设置端口
}

成功打印文件

 错误打印文件

 错误文件显示页面很难看而且用户也不能得到任何信息 这不是我们想要的

下面我们优化一下代码 让错误信息分内部信息及外部信息(用户看的)

这是优化完的代码

package filelisting

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

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
}
package main

import (
	"net/http"
	"awesomeProject1/test/filelistingserver/filelisting"
	"os"
	"github.com/gpmgo/gopm/modules/log"
)

type appHandle func(writer http.ResponseWriter,request *http.Request)error // 将函数定义为类型 并且函数参数,返回值与HandleFileList完全相同
																			//appHandle类型唯一作用是用来传输错误信息
func errWrapper(handler appHandle) func (http.ResponseWriter, *http.Request){	//此函数的作用是错误信息统一打印 并且返回正确函数
	return func(writer http.ResponseWriter,request *http.Request){ //闭包
		err := handler(writer, request)								//这个函数的关键在于这行代码 如果有错误信息则进行下面代码反之返回正确函数
		code := http.StatusOK
		if err != nil{										//假设有错误信息 进行统一的分类 这段代码只有两个错误信息 实际情况可以自行添加
			log.Warn("Error handleing request: %s", err.Error()) //这是给开发人员看的错误信息比较详细
			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)
	}
}

页面错误报告 

 内部错误报告

 

由此 错误报告实现统一 

从上述代码中不难发现  我们每次err != nil后都会panic 但是在实际开发中应该尽量少的使用panic

因为panic代表你不知道这个问题解决 如果你知道怎么解决 最好使用err.Error fmt 之类的打印出来

那如果实在要使用panic最好用recover接住 下面是一个recover的例子

package main

import (
	"fmt"
)

func tryrecover(){
	defer func (){   //recover的用法是在defer一个匿名函数 在函数里面进行错误信息打印
		r := recover()					
		if err, ok := r.(error); ok{   //能够识别的错误信息返回true
			fmt.Println("Error :", err)
		}else{						//反之继续panic
			panic(r)
		}
	}()					//注意匿名函数后面有小括号
	//panic(errors.New("This is an error"))   //每一个panic返回值不同
 
	//b := 0                                 //每一个panic返回值不同
	//a := 5/b
	//fmt.Println(a)

	panic(123)                               //每一个panic返回值不同
}

func main(){
	tryrecover()
}

下面咱们知道defer使用方法后 我们来优化一下网页打开文件代码 这篇主要讲述的是各种错误处理

package filelisting

import (
	"net/http"
	"os"
	"io/ioutil"
	"strings"
)
const prefix = "/list/"
type userHandle string            //给接口使用的类型
func (u userHandle)Error()string{  //这个函数是给开发人员看的 实际也是Message只不过名字不同而已
	return u.Message()				//返回值就是Messa
}
func (u userHandle)Message()string{	//这个函数式给用户看的 返回的是调用者的错误信息 调用者保存的错误信息在下方例子中
	return string(u)
}

func HandleFileList(writer http.ResponseWriter,request *http.Request) error { // 优化完后是在尾部添加返回值 进行统一错误管理
	if strings.Index(request.URL.Path,prefix) != 0{				//检查用户是否使用/list/查找
		return userHandle("panic this path not "+prefix)        //接口的整个数据核心 将错误信息放入userHandle中
	}
	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
}
package main

import (
	"net/http"
	"awesomeProject1/test/filelistingserver/filelisting"
	"os"
	"awesomeProject1/golog"
	"log"
)

type appHandle func(writer http.ResponseWriter,request *http.Request)error // 将函数定义为类型 并且函数参数,返回值与HandleFileList完全相同
																			//appHandle类型唯一作用是用来传输错误信息
func errWrapper(handler appHandle) func (http.ResponseWriter, *http.Request){	//此函数的作用是错误信息统一打印 并且返回正确函数
	return func(writer http.ResponseWriter,request *http.Request){ //闭包
		err := handler(writer, request)								//这个函数的关键在于这行代码 如果有错误信息则进行下面代码反之返回正确函数
		defer func(){   //截断http自带defer保护 自己设置想要的内容
			if r:= recover(); r != nil{     //判断信息是否为空
				log.Printf("Panic: %v",r)   //打印给开发人员详细信息
				http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)//打印给用户错误报告
			}
		}()
		code := http.StatusOK
		if err != nil{										//假设有错误信息 进行统一的分类 这段代码只有两个错误信息 实际情况可以自行添加
			golog.Printf("Error handleing request: %s", err.Error()) //这是给开发人员看的错误信息比较详细
			if usererr,ok := err.(userHandle); ok{    //判断接口是否有信息并赋值给变量usererr
				http.Error(writer,usererr.Message(),http.StatusBadRequest)   //将给用户看的错误信息及错误报告返回给用户
				return
			}
			switch {
			case os.IsNotExist(err):
				code = http.StatusNotFound
			default :
				code = http.StatusInternalServerError
			}
			http.Error(writer,								//这是给用户看的信息
				http.StatusText(code),
				code)
		}
	}
}

type userHandle interface{ // 定义接口及方法  实现在handler.go内 先看handler.go
	error                    //自带包函数 借用
	Message() string        //用户专用返回错误信息
}

func main(){
	http.HandleFunc("/",errWrapper(filelisting.HandleFileList))
	err :=http.ListenAndServe(":8888",nil)
	if err != nil{
		panic(err)
	}
}

从代码中我们确切的看到了错误信息自由掌握的方法

错误处理先到这里

猜你喜欢

转载自blog.csdn.net/weixin_42654444/article/details/82154683