1. defer保證在函數(shù)結(jié)束時(shí)發(fā)生.
2. defer列表為先進(jìn)后出
3. 參數(shù)在defer語(yǔ)句時(shí)計(jì)算.
下面來(lái)看一個(gè)例子: 寫(xiě)入文件
package main import ( "aaa/functional/fbi" "bufio" "fmt" "os" ) // 我要寫(xiě)文件 func writeFile() { file, err := os.Create("test.txt") if err != nil { panic("error") } defer file.Close() w := bufio.NewWriter(file) defer w.Flush() f := fbi.Feibonaccq() for i := 0; i 20; i++ { fmt.Fprintln(w, f()) } } func main() { writeFile() }
package fbi func Feibonaccq() func() int { x, y := 0, 1 return func() int { x, y = y, x+y return x } }
將斐波那契數(shù)列寫(xiě)入文件. 這里有兩個(gè)資源使用. 1. 創(chuàng)建文件, 然后文件關(guān)閉. 2. 寫(xiě)入資源, 將資源從緩存中刷入文件. 這兩個(gè)操作都應(yīng)該應(yīng)該是成對(duì)出現(xiàn)的, 因此, 用defer 語(yǔ)句, 避免后面寫(xiě)著寫(xiě)著忘了, 也保證即使出錯(cuò)了, 也能夠執(zhí)行defer語(yǔ)句的內(nèi)容
那么參數(shù)在defer語(yǔ)句時(shí)計(jì)算 是什么意思呢?
func tryDefer() { for i := 0; i 10 ; i++ { defer fmt.Println(i) } }
打印結(jié)果:
9
8
7
6
5
4
3
2
1
0
所謂的錯(cuò)誤處理, 就是處理已知的錯(cuò)誤, 不要拋出panic這樣導(dǎo)致系統(tǒng)掛掉的錯(cuò)誤發(fā)生.
比如下面的操作:
package main import ( "aaa/functional/fbi" "bufio" "fmt" "os" ) // 我要寫(xiě)文件 func writeFile(filename string) { // os.O_EXCL|os.O_CREATE創(chuàng)建一個(gè)新文件, 并且他必須不存在 file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666) // 這時(shí)候打印panic就不太友好. 我們可以對(duì)錯(cuò)誤類(lèi)型進(jìn)行處理 /*if err != nil { panic("error") }*/ // 這里就對(duì)錯(cuò)誤的類(lèi)型進(jìn)行了捕獲處理. if err, ok := err.(*os.PathError); !ok { fmt.Println("未知錯(cuò)誤") } else { fmt.Printf("%s, %s, %s", err.Path, err.Op, err.Err) } defer file.Close() w := bufio.NewWriter(file) defer w.Flush() f := fbi.Feibonaccq() for i := 0; i 20; i++ { fmt.Fprintln(w, f()) } } func main() { writeFile("test.txt") }
紅色字體部分就是對(duì)錯(cuò)誤進(jìn)行了捕獲處理.
下面模擬一個(gè)web服務(wù)器, 在瀏覽器地址欄輸入文件的url, 然后顯示文件的內(nèi)容. 比如斐波那契數(shù)列的文件
package main import ( "io/ioutil" "net/http" "os" ) // 我們來(lái)模擬一個(gè)web服務(wù)器. 在url上輸入一個(gè)地址, 然后顯示文件內(nèi)容 // 做一個(gè)顯示文件的web server func main() { http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) { // 獲取url路徑, 路徑是/list/之后的部分 path := request.URL.Path[len("/list/"):] // 打開(kāi)文件 file, err := os.Open(path) if err != nil { panic("err") } defer file.Close() // 讀出文件 b, err := ioutil.ReadAll(file) if err != nil { panic("err") } // 寫(xiě)入文件到頁(yè)面 writer.Write(b) }) // 監(jiān)聽(tīng)端口:8888 err := http.ListenAndServe(":8888", nil) if err != nil { panic("err") } }
這里面主要注意一下我們對(duì)錯(cuò)誤的處理. 都是直接打出panic. 這樣是很不友好的.
如果頁(yè)面輸入的文件路徑不對(duì), 則直接404
按照之前第二步說(shuō)的, 我們應(yīng)該對(duì)panic進(jìn)行處理. 比如打開(kāi)文件的操作, 我們改為如下
// 打開(kāi)文件 file, err := os.Open(path) if err != nil { http.Error(writer, err.Error(), http.StatusInternalServerError) return } defer file.Close()
這樣就好多了, 起碼程序不會(huì)直接拋出異常
這是將系統(tǒng)的錯(cuò)誤直接打出了, 比上面好一些, 但也不是特別友好, 通常我們不希望吧系統(tǒng)內(nèi)部錯(cuò)誤輸出出來(lái). 我們希望經(jīng)過(guò)包裝后輸出錯(cuò)誤
于是做了如下修改.
第一步: 將http.handleFunc中的函數(shù)部分提出來(lái), 這部分是業(yè)務(wù)邏輯.
提出來(lái)以后做了如下修改. 1. 函數(shù)增加一個(gè)返回值error. 2. 遇到錯(cuò)誤,直接return. 如下紅色標(biāo)出部分
package fileListener import ( "io/ioutil" "net/http" "os" ) func FileHandler(writer http.ResponseWriter, request *http.Request) error{ // 獲取url路徑, 路徑是/list/之后的部分 path := request.URL.Path[len("/list/"):] // 打開(kāi)文件 file, err := os.Open(path) if err != nil { return err } defer file.Close() // 讀出文件 b, err := ioutil.ReadAll(file) if err != nil { return err } // 寫(xiě)入文件到頁(yè)面 writer.Write(b) return nil }
第二: 封裝錯(cuò)誤內(nèi)容
這里就體現(xiàn)了函數(shù)式編程的特點(diǎn), 靈活
// 定義一個(gè)函數(shù)類(lèi)型的結(jié)構(gòu), 返回值是erro type Handler func(writer http.ResponseWriter, request *http.Request) error // 封裝error func WrapHandler(handler Handler) func (http.ResponseWriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { // 執(zhí)行原來(lái)的邏輯. 然后增加error的錯(cuò)誤處理 err := handler(writer, request) if err != nil { code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound case os.IsPermission(err): code = http.StatusServiceUnavailable default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) } } }
調(diào)用的部分
// 我們來(lái)模擬一個(gè)web服務(wù)器. 在url上輸入一個(gè)地址, 然后顯示文件內(nèi)容 // 做一個(gè)顯示文件的web server func main() { http.HandleFunc("/list/", WrapHandler(fileListener.FileHandler)) // 監(jiān)聽(tīng)端口:8888 err := http.ListenAndServe(":8888", nil) if err != nil { panic("err") } }
這樣, 當(dāng)我們?cè)俅屋斎脲e(cuò)誤的文件路徑時(shí), 提示信息如下:
發(fā)生panic的時(shí)候, 會(huì)做那些事呢?
1. 停止當(dāng)前函數(shù)的執(zhí)行
2. 一直向上返回, 執(zhí)行每一層的defer
3. 如果沒(méi)有遇到recover, 程序就退出
1. 在defer 中調(diào)用
2. 獲取panic的值
3. 如果無(wú)法處理, 可以重新panic
package main import ( "fmt" "github.com/pkg/errors" ) func tryRecover() { defer func(){ r := recover() if r, ok := r.(error); ok { fmt.Println("error 發(fā)生", r.Error()) } else { panic(fmt.Sprintf("未知錯(cuò)誤:%v", r)) } }() panic(errors.New("錯(cuò)誤")) } func main() { tryRecover() }
第五條的案例, 我們進(jìn)行了error的統(tǒng)一管理, 但是還沒(méi)有對(duì)其他異常進(jìn)行recover, 還有可能導(dǎo)致程序崩潰. 比如http://localhost:8888/abc. 繼續(xù)優(yōu)化代碼.
這樣很不友好, 我們?cè)诳纯纯刂婆_(tái), 發(fā)現(xiàn)程序并沒(méi)有掛掉, 這是為什么呢? 想象一下, 應(yīng)該是程序自動(dòng)給我們r(jià)ecover了.
我們來(lái)看看server.go
原來(lái)server.go已經(jīng)幫我們r(jià)ecover了, recover后并不是中斷進(jìn)程, 而是打印輸出錯(cuò)誤日志. 雖然如此, 但頁(yè)面顯示依然很難看. 因此我們要做兩件事
1. 如果出現(xiàn)異常, 我們自己進(jìn)行recover, 那么他就不會(huì)走系統(tǒng)定義的recover了. 這還不夠, 這只是說(shuō)控制臺(tái)不會(huì)再打印出一大堆藍(lán)色異常代碼了. 我們還有做第二件事
2. 將出現(xiàn)異常的位置捕獲出來(lái), 并且, 打印到頁(yè)面
第一步: 自定一定recover, 代替server.go中的recover
// 封裝error func WrapError(handler Handler) func (http.ResponseWriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { defer func(){ if r := recover(); r != nil { fmt.Println("發(fā)生錯(cuò)誤") http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() // 執(zhí)行原來(lái)的邏輯. 然后增加error的錯(cuò)誤處理 err := handler(writer, request) if err != nil { code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound case os.IsPermission(err): code = http.StatusServiceUnavailable default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) } } }
這樣異常就被我們捕獲了, 頁(yè)面打印出
這樣就好看多了. 我們?cè)趯?duì)代碼進(jìn)行優(yōu)化
我們將發(fā)生異常的地方進(jìn)行處理
func FileHandler(writer http.ResponseWriter, request *http.Request) error { // 獲取url路徑, 路徑是/list/之后的部分 if c := strings.Index(request.URL.Path, "/list/"); c != 0 { return errors.New("url 不是已list開(kāi)頭") } path := request.URL.Path[len("/list/"):] // 打開(kāi)文件 file, err := os.Open(path) if err != nil { return err } defer file.Close() // 讀出文件 b, err := ioutil.ReadAll(file) if err != nil { return err } // 寫(xiě)入文件到頁(yè)面 writer.Write(b) return nil }
頁(yè)面打印效果
我們發(fā)現(xiàn)這個(gè)打印的還是系統(tǒng)給出的錯(cuò)誤異常. 那么,我們有沒(méi)有辦法, 把這個(gè)異常打印出來(lái)呢?
if c := strings.Index(request.URL.Path, "/list/"); c != 0 { return errors.New("url 不是已list開(kāi)頭") }
我們自己來(lái)定義一個(gè)異常處理的接口
type userError interface { error // 系統(tǒng)異常 Message() string // 用戶(hù)自定義異常 }
接口定義好了, 在哪里用呢? 你想打印出自己的異常信息, 那就不能打印系統(tǒng)的. 自定義信息在系統(tǒng)異常之前判斷
// 執(zhí)行原來(lái)的邏輯. 然后增加error的錯(cuò)誤處理 err := handler(writer, request) if err != nil { if userErr, ok := err.(userError); ok { http.Error(writer, userErr.Message(), http.StatusBadRequest) return } code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound case os.IsPermission(err): code = http.StatusServiceUnavailable default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) }
接下來(lái)是具體實(shí)現(xiàn)了, 現(xiàn)在用戶(hù)想要實(shí)現(xiàn)自定義一個(gè)userError. 然后設(shè)置異常類(lèi)型為userError
type userError string func (u userError) Error() string{ return u.Message() } func (u userError) Message() string { return string(u) }
func FileHandler(writer http.ResponseWriter, request *http.Request) error { // 獲取url路徑, 路徑是/list/之后的部分 if c := strings.Index(request.URL.Path, "/list/"); c != 0 { return userError("url 不是已list開(kāi)頭") } path := request.URL.Path[len("/list/"):] // 打開(kāi)文件 file, err := os.Open(path) if err != nil { return err } defer file.Close() // 讀出文件 b, err := ioutil.ReadAll(file) if err != nil { return err } // 寫(xiě)入文件到頁(yè)面 writer.Write(b) return nil }
這樣一個(gè)實(shí)現(xiàn)自定義打印異常的功能就做好了. 異常也是可以封裝的.
最后再來(lái)梳理這個(gè)小案例:
1. 我們有一個(gè)想法, 模擬web請(qǐng)求, 在瀏覽器url上輸入一個(gè)文件路徑, 打印文件的內(nèi)容
2. 內(nèi)容可能有錯(cuò)誤, 進(jìn)行異常處理.
3. 有時(shí)候異常拋出的是系統(tǒng)給出, 我們自己對(duì)異常進(jìn)行recover, 然后打印出來(lái)
4. 打印自定義異常.
以下是完整代碼
package handling import ( "io/ioutil" "net/http" "os" "strings" ) type UserError struct { Content string } func (u UserError) Error() string { return u.Message() } func (u UserError) Message() string { return u.Content } func Hanldering(writer http.ResponseWriter, request *http.Request) error { // 獲取url, list之后的就是url if s := strings.Index(request.URL.Path, "/list/"); s != 0 { return UserError{"path error, /list/"} } url := request.URL.Path[len("/list/"):] // 根據(jù)url打開(kāi)文件 file, err := os.Open(url) if err != nil { return os.ErrNotExist } defer file.Close() // 打開(kāi)以后把文件內(nèi)容讀出來(lái) f, err := ioutil.ReadAll(file) if err != nil { return os.ErrPermission } // 讀出來(lái)以后, 寫(xiě)入到頁(yè)面 writer.Write(f) return nil }
package main import ( "aaa/handlerError/linstenerFile/handling" "github.com/siddontang/go/log" "net/http" "os" ) type ErrorHandlering func(writer http.ResponseWriter, request *http.Request) error func WrapError(handler ErrorHandlering) func(http.ResponseWriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { defer func() { if r := recover(); r != nil { log.Warn("other error") http.Error(writer, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } }() err := handler(writer, request) //自定義異常處理 // 錯(cuò)誤處理 if err != nil { if userErr, ok := err.(UserError); ok { log.Warn("user error:", userErr.Message()) http.Error(writer, userErr.Message(), http.StatusBadRequest) return } code := http.StatusOK switch err { case os.ErrNotExist: code = http.StatusNotFound case os.ErrPermission: code = http.StatusBadRequest default: code = http.StatusInternalServerError } http.Error(writer, http.StatusText(code), code) } } } type UserError interface { error Message() string } func main() { // 模擬web請(qǐng)求 http.HandleFunc("/", WrapError(handling.Hanldering)) // 指定服務(wù)端口 http.ListenAndServe(":8888", nil) }
以上就是詳解Go語(yǔ)言的錯(cuò)誤處理和資源管理的詳細(xì)內(nèi)容,更多關(guān)于Go 錯(cuò)誤處理 資源管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
標(biāo)簽:蘭州 欽州 雞西 吐魯番 重慶 汕頭 銅川 梅河口
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳解Go語(yǔ)言的錯(cuò)誤處理和資源管理》,本文關(guān)鍵詞 詳解,語(yǔ)言,的,錯(cuò)誤,處理,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。