Go资源与出错处理
1、defer延迟语句
确保调用在函数结束时发生:延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回
参数在defer语句时计算
defer列表为先进后出
error vs panic : 意料之中用error比如文件打不开,意料之外用panic比如数组越界
延迟函数
可在函数中添加多个defer语句,函数执行到最后,defer语句会按照逆序执行,最后该函数返回。
注:比如打开资源操作遇见错误需提前返回,返回前应当关闭相应资源,这时候可用defer。
注意:示例用到defer+panic+recover 用到Type Assertion 函数式编程很重要
例子1
创建目录:errorhandling下面再创建目录defer
//defer里面是相当于有一个栈 先进后出 defer好处:不怕中间有return
func tryDefer() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) //第一种:return panic("eror occurred") //第二种 fmt.Println(4) }//输出是3 2 1
其他
- 延迟方法
defer也可以用于延迟一个方法调用。main函数中如果有defer将不会执行下面语句。
- 延迟参数
延迟函数的参数在执行延迟语句时被执行。
func printA(a int) { fmt.Println("value of a in deferred function", a) } func main() { a := 5 defer printA(a) a = 10 fmt.Println("value of a before deferred function call", a) } 输出: value of a before deferred function call 10 value of a in deferred function 5
-
堆栈延迟
一个函数有多个延迟调用时,它们被添加到一个堆栈中,并在后进先出中顺序中执行。
func main() { name := "Naveen" fmt.Printf("Orignal String: %s\n", string(name)) fmt.Printf("Reversed String: ") for _, v := range []rune(name) { defer fmt.Printf("%c", v) } } 输出: Orignal String: Naveen Reversed String: neevaN
例子2
详情参加functional https://www.cnblogs.com/ycx95/p/9362175.html
package main import ( "fmt" "os" "bufio" "imooc.com/ccmouse/learngo/functional/fib" ) func tryDefer() { for i := 0; i < 100; i++ { defer fmt.Println(i) if i == 30 { //由于现进后出 输出就是:30 29 ... 1 // Uncomment panic to see // how it works with defer // panic("printed too many") } } } func writeFile(filename string) { file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666) //err只是一个值 可以针对值进行处理 if err != nil { //错误处理 细致的错误处理 如果文件存在:输出会提示文件存在 if pathError, ok := err.(*os.PathError); !ok { panic(err) } else { fmt.Printf("%s, %s, %s\n", pathError.Op, pathError.Path, pathError.Err) } return } defer file.Close() //file写完了要关闭 writer := bufio.NewWriter(file) // 直接file写文件很慢,因此用bufio包装一下 defer writer.Flush() //导进新创建的fib.txt文件里面 f := fib.Fibonacci() for i := 0; i < 20; i++ { //写前20个fib数列 fmt.Fprintln(writer, f()) } } func main() { tryDefer() writeFile("fib.txt") //运行程序这里会创建一个fib.txt文件 }
2. 错误处理
Go中错误也是一种类型,错误用内置的error类型表示。错误可存储在变量中,从函数中返回。
注:一个打开文件的功能函数 func Open(name string) (file *File, err error) 若文件成功打开,则将返回文件处理,否则返回一个非nil错误。
错误类型表示
type error interface { Error() string //任何实现该接口的类型都可以作为一个错误调用 该方法提供了对错误的描述 }
错误处理一:人为处理
制造一个错误(程序会挂掉)
将原来的: file,err := os.Create(filename) //Create是openfile加了一些状态 直接修改为: file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666) //0666是一个权限
处理挂掉的错误:如何对err进行细化处理(详见上面代码writefile的error处理部分
自己写error:err = errors.New("this is a custom error“) 其实error就是一个interface
错误处理: file,err := os.Open("abc.txt") if err!= nil { if pathError,ok := err.(*os.PathError);ok { fmt.Println(pathError.Err) }else{ fmt.Println("unknown error",err) }
错误处理二:统一错误处理
新建目录:errorhanding目录下创建 filelistingserver 目录
handler.go ( 位置 errorhandling/filelistingserver/filelisting)
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 { fmt.Println() 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(prefix):] file, err := os.Open(path) if err != nil { return err //有错就return出去外面有处理 } defer file.Close() all, err := ioutil.ReadAll(file) if err != nil { return err } writer.Write(all) return nil }
web.go(位置 errorhandling/filelistingserver)
package main import ( "log" //这里视频中所写是 github.com/gpmgo/gopm/modules/log "net/http" _ "net/http/pprof" "os" "imooc.com/ccmouse/learngo/errhandling/filelistingserver/filelisting" ) //返回一个err type appHandler func(writer http.ResponseWriter, request *http.Request) error //包装errHandler返回的err,然后返回一个函数 特点:输入是一个函数 输出也是一个函数 func errWrapper( handler appHandler) func( http.ResponseWriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { // panic defer func() { if r := recover(); r != nil { log.Printf("Panic: %v", r) http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() err := handler(writer, request) //处理err进行处理 if err != nil { log.Printf("Error occurred "+ "handling request: %s", err.Error()) // user error if userErr, ok := err.(userError); ok { http.Error(writer, userErr.Message(), http.StatusBadRequest) return } // system error code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound case os.IsPermission(err): code = http.StatusForbidden default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) } } } type userError interface { error Message() string } func main() { http.HandleFunc("/", errWrapper(filelisting.HandleFileList)) err := http.ListenAndServe(":8888", nil) //开服务器 打开服务器输入:localhost:8888/list/fib.txt if err != nil { panic(err) } }
//测试时候:管理员目录时候cp fib.txt fib2.txt 浏览器可以访问 让别人没有权限:chmod 500 fib2.txt 浏览器输出:Forbidden
errwrapper_test.go(位置 errorhandling/filelistingserver)
package main import ( "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "strings" "testing" ) func errPanic(_ http.ResponseWriter, _ *http.Request) error { panic(123) } type testingUserError string func (e testingUserError) Error() string { return e.Message() } func (e testingUserError) Message() string { return string(e) } func errUserError(_ http.ResponseWriter, _ *http.Request) error { return testingUserError("user error") } func errNotFound(_ http.ResponseWriter, _ *http.Request) error { return os.ErrNotExist } func errNoPermission(_ http.ResponseWriter, _ *http.Request) error { return os.ErrPermission } func errUnknown(_ http.ResponseWriter, _ *http.Request) error { return errors.New("unknown error") } func noError(writer http.ResponseWriter, _ *http.Request) error { fmt.Fprintln(writer, "no error") return nil } var tests = []struct { h appHandler code int message string }{ {errPanic, 500, "Internal Server Error"}, {errUserError, 400, "user error"}, {errNotFound, 404, "Not Found"}, {errNoPermission, 403, "Forbidden"}, {errUnknown, 500, "Internal Server Error"}, {noError, 200, "no error"}, } func TestErrWrapper(t *testing.T) { for _, tt := range tests { f := errWrapper(tt.h) response := httptest.NewRecorder() request := httptest.NewRequest( http.MethodGet, "http://www.imooc.com", nil) f(response, request) verifyResponse(response.Result(), tt.code, tt.message, t) } } func TestErrWrapperInServer(t *testing.T) { for _, tt := range tests { f := errWrapper(tt.h) server := httptest.NewServer( http.HandlerFunc(f)) resp, _ := http.Get(server.URL) verifyResponse( resp, tt.code, tt.message, t) } } func verifyResponse(resp *http.Response, expectedCode int, expectedMsg string, t *testing.T) { b, _ := ioutil.ReadAll(resp.Body) body := strings.Trim(string(b), "\n") if resp.StatusCode != expectedCode || body != expectedMsg { t.Errorf("expect (%d, %s); "+ "got (%d, %s)", expectedCode, expectedMsg, resp.StatusCode, body) } }
3. panic
Go没有像Java一样的异常机制,不能抛出异常,而是使用了panic和recover机制。
注意:不能经常用
panic是一个内建函数,停止当前函数执行,一直向上返回,执行每一层的defer,如果没有遇见recover则程序退出
4.recover
与panic对应,可让进入令人恐慌的流程中的goroutine恢复过来,仅在延迟函数中有效。正常执行过程中,调用recover返回nil,无任何效果。
仅在defer调用中使用,获取panic的值,如果无法处理可重新panic
例子:
package main import ( "fmt" ) func tryRecover() { defer func() { //这里写一个匿名函数 注意最后加的() r := recover() //哈哈,这里是recover if r == nil { fmt.Println("Nothing to recover. " + "Please try uncomment errors " + "below.") return } if err, ok := r.(error); ok { fmt.Println("Error occurred:", err) //的确是err } else { panic(fmt.Sprintf( "I don't know what to do: %v", r)) } }() // Uncomment each block to see different panic // scenarios. // Normal error //panic(errors.New("this is an error")) // Division by zero //b := 0 //a := 5 / b //fmt.Println(a) // Causes re-panic //panic(123) } func main() { tryRecover() }