延迟返回关键字 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)
}
}
从代码中我们确切的看到了错误信息自由掌握的方法
错误处理先到这里