目錄
- 一、defer
- 二、錯誤處理
- 三、統(tǒng)一錯誤處理的邏輯
- 四、panic
- 五、recover
- 六、error vs panic
- 七、錯誤處理綜合示例
一、defer
1. defer保證在函數(shù)結(jié)束時發(fā)生.
2. defer列表為先進后出
3. 參數(shù)在defer語句時計算.
下面來看一個例子: 寫入文件
package main
import (
"aaa/functional/fbi"
"bufio"
"fmt"
"os"
)
// 我要寫文件
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ù)列寫入文件. 這里有兩個資源使用. 1. 創(chuàng)建文件, 然后文件關(guān)閉. 2. 寫入資源, 將資源從緩存中刷入文件. 這兩個操作都應該應該是成對出現(xiàn)的, 因此, 用defer 語句, 避免后面寫著寫著忘了, 也保證即使出錯了, 也能夠執(zhí)行defer語句的內(nèi)容
那么參數(shù)在defer語句時計算 是什么意思呢?
func tryDefer() {
for i := 0; i 10 ; i++ {
defer fmt.Println(i)
}
}
打印結(jié)果:
9
8
7
6
5
4
3
2
1
0
二、錯誤處理
所謂的錯誤處理, 就是處理已知的錯誤, 不要拋出panic這樣導致系統(tǒng)掛掉的錯誤發(fā)生.
比如下面的操作:
package main
import (
"aaa/functional/fbi"
"bufio"
"fmt"
"os"
)
// 我要寫文件
func writeFile(filename string) {
// os.O_EXCL|os.O_CREATE創(chuàng)建一個新文件, 并且他必須不存在
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
// 這時候打印panic就不太友好. 我們可以對錯誤類型進行處理
/*if err != nil {
panic("error")
}*/
// 這里就對錯誤的類型進行了捕獲處理.
if err, ok := err.(*os.PathError); !ok {
fmt.Println("未知錯誤")
} 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")
}
紅色字體部分就是對錯誤進行了捕獲處理.
三、統(tǒng)一錯誤處理的邏輯
下面模擬一個web服務器, 在瀏覽器地址欄輸入文件的url, 然后顯示文件的內(nèi)容. 比如斐波那契數(shù)列的文件
package main
import (
"io/ioutil"
"net/http"
"os"
)
// 我們來模擬一個web服務器. 在url上輸入一個地址, 然后顯示文件內(nèi)容
// 做一個顯示文件的web server
func main() {
http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
// 獲取url路徑, 路徑是/list/之后的部分
path := request.URL.Path[len("/list/"):]
// 打開文件
file, err := os.Open(path)
if err != nil {
panic("err")
}
defer file.Close()
// 讀出文件
b, err := ioutil.ReadAll(file)
if err != nil {
panic("err")
}
// 寫入文件到頁面
writer.Write(b)
})
// 監(jiān)聽端口:8888
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic("err")
}
}
這里面主要注意一下我們對錯誤的處理. 都是直接打出panic. 這樣是很不友好的.
如果頁面輸入的文件路徑不對, 則直接404
按照之前第二步說的, 我們應該對panic進行處理. 比如打開文件的操作, 我們改為如下
// 打開文件
file, err := os.Open(path)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError) return
}
defer file.Close()
這樣就好多了, 起碼程序不會直接拋出異常
這是將系統(tǒng)的錯誤直接打出了, 比上面好一些, 但也不是特別友好, 通常我們不希望吧系統(tǒng)內(nèi)部錯誤輸出出來. 我們希望經(jīng)過包裝后輸出錯誤
于是做了如下修改.
第一步: 將http.handleFunc中的函數(shù)部分提出來, 這部分是業(yè)務邏輯.
提出來以后做了如下修改. 1. 函數(shù)增加一個返回值error. 2. 遇到錯誤,直接return. 如下紅色標出部分
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/"):]
// 打開文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 讀出文件
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
// 寫入文件到頁面
writer.Write(b)
return nil
}
第二: 封裝錯誤內(nèi)容
這里就體現(xiàn)了函數(shù)式編程的特點, 靈活
// 定義一個函數(shù)類型的結(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í)行原來的邏輯. 然后增加error的錯誤處理
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)用的部分
// 我們來模擬一個web服務器. 在url上輸入一個地址, 然后顯示文件內(nèi)容
// 做一個顯示文件的web server
func main() {
http.HandleFunc("/list/", WrapHandler(fileListener.FileHandler))
// 監(jiān)聽端口:8888
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic("err")
}
}
這樣, 當我們再次輸入錯誤的文件路徑時, 提示信息如下:
四、panic
發(fā)生panic的時候, 會做那些事呢?
1. 停止當前函數(shù)的執(zhí)行
2. 一直向上返回, 執(zhí)行每一層的defer
3. 如果沒有遇到recover, 程序就退出
五、recover
1. 在defer 中調(diào)用
2. 獲取panic的值
3. 如果無法處理, 可以重新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("未知錯誤:%v", r))
}
}()
panic(errors.New("錯誤"))
}
func main() {
tryRecover()
}
六、error vs panic
七、錯誤處理綜合示例
第五條的案例, 我們進行了error的統(tǒng)一管理, 但是還沒有對其他異常進行recover, 還有可能導致程序崩潰. 比如http://localhost:8888/abc. 繼續(xù)優(yōu)化代碼.
這樣很不友好, 我們在看看控制臺, 發(fā)現(xiàn)程序并沒有掛掉, 這是為什么呢? 想象一下, 應該是程序自動給我們recover了.
我們來看看server.go
原來server.go已經(jīng)幫我們recover了, recover后并不是中斷進程, 而是打印輸出錯誤日志. 雖然如此, 但頁面顯示依然很難看. 因此我們要做兩件事
1. 如果出現(xiàn)異常, 我們自己進行recover, 那么他就不會走系統(tǒng)定義的recover了. 這還不夠, 這只是說控制臺不會再打印出一大堆藍色異常代碼了. 我們還有做第二件事
2. 將出現(xiàn)異常的位置捕獲出來, 并且, 打印到頁面
第一步: 自定一定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ā)生錯誤")
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}()
// 執(zhí)行原來的邏輯. 然后增加error的錯誤處理
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ōu)化
我們將發(fā)生異常的地方進行處理
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開頭")
}
path := request.URL.Path[len("/list/"):]
// 打開文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 讀出文件
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
// 寫入文件到頁面
writer.Write(b)
return nil
}
頁面打印效果
我們發(fā)現(xiàn)這個打印的還是系統(tǒng)給出的錯誤異常. 那么,我們有沒有辦法, 把這個異常打印出來呢?
if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
return errors.New("url 不是已list開頭")
}
我們自己來定義一個異常處理的接口
type userError interface {
error // 系統(tǒng)異常
Message() string // 用戶自定義異常
}
接口定義好了, 在哪里用呢? 你想打印出自己的異常信息, 那就不能打印系統(tǒng)的. 自定義信息在系統(tǒng)異常之前判斷
// 執(zhí)行原來的邏輯. 然后增加error的錯誤處理
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)
}
接下來是具體實現(xiàn)了, 現(xiàn)在用戶想要實現(xiàn)自定義一個userError. 然后設(shè)置異常類型為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開頭")
}
path := request.URL.Path[len("/list/"):]
// 打開文件
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
// 讀出文件
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
// 寫入文件到頁面
writer.Write(b)
return nil
}
這樣一個實現(xiàn)自定義打印異常的功能就做好了. 異常也是可以封裝的.
最后再來梳理這個小案例:
1. 我們有一個想法, 模擬web請求, 在瀏覽器url上輸入一個文件路徑, 打印文件的內(nèi)容
2. 內(nèi)容可能有錯誤, 進行異常處理.
3. 有時候異常拋出的是系統(tǒng)給出, 我們自己對異常進行recover, 然后打印出來
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打開文件
file, err := os.Open(url)
if err != nil {
return os.ErrNotExist
}
defer file.Close()
// 打開以后把文件內(nèi)容讀出來
f, err := ioutil.ReadAll(file)
if err != nil {
return os.ErrPermission
}
// 讀出來以后, 寫入到頁面
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)
//自定義異常處理
// 錯誤處理
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請求
http.HandleFunc("/", WrapError(handling.Hanldering))
// 指定服務端口
http.ListenAndServe(":8888", nil)
}
以上就是詳解Go語言的錯誤處理和資源管理的詳細內(nèi)容,更多關(guān)于Go 錯誤處理 資源管理的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章:- 詳解Go多協(xié)程并發(fā)環(huán)境下的錯誤處理
- 關(guān)于Mongodb參數(shù)說明與常見錯誤處理的總結(jié)
- Golang巧用defer進行錯誤處理的方法
- Go語言中更優(yōu)雅的錯誤處理
- GO語言標準錯誤處理機制error用法實例
- Django靜態(tài)資源部署404問題解決方案
- Django跨域資源共享問題(推薦)
- 基于Django靜態(tài)資源部署404的解決方法
- Django靜態(tài)資源URL STATIC_ROOT的配置方法