主頁 > 知識庫 > 詳解Go語言的錯(cuò)誤處理和資源管理

詳解Go語言的錯(cuò)誤處理和資源管理

熱門標(biāo)簽:網(wǎng)站排名優(yōu)化 AI電銷 呼叫中心市場需求 地方門戶網(wǎng)站 百度競價(jià)排名 Linux服務(wù)器 鐵路電話系統(tǒng) 服務(wù)外包

一、defer

1. defer保證在函數(shù)結(jié)束時(shí)發(fā)生.

2. defer列表為先進(jìn)后出

3. 參數(shù)在defer語句時(shí)計(jì)算.

下面來看一個(gè)例子: 寫入文件

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ù)列寫入文件. 這里有兩個(gè)資源使用. 1. 創(chuàng)建文件, 然后文件關(guān)閉. 2. 寫入資源, 將資源從緩存中刷入文件. 這兩個(gè)操作都應(yīng)該應(yīng)該是成對出現(xiàn)的, 因此, 用defer 語句, 避免后面寫著寫著忘了, 也保證即使出錯(cuò)了, 也能夠執(zhí)行defer語句的內(nèi)容

那么參數(shù)在defer語句時(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ò)誤處理, 就是處理已知的錯(cuò)誤, 不要拋出panic這樣導(dǎo)致系統(tǒng)掛掉的錯(cuò)誤發(fā)生.

比如下面的操作:

package main

import (
    "aaa/functional/fbi"
    "bufio"
    "fmt"
    "os"
)

// 我要寫文件
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就不太友好. 我們可以對錯(cuò)誤類型進(jìn)行處理
    /*if err != nil {
        panic("error")
    }*/

    // 這里就對錯(cuò)誤的類型進(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")
}

紅色字體部分就是對錯(cuò)誤進(jìn)行了捕獲處理.

三、統(tǒng)一錯(cuò)誤處理的邏輯

下面模擬一個(gè)web服務(wù)器, 在瀏覽器地址欄輸入文件的url, 然后顯示文件的內(nèi)容. 比如斐波那契數(shù)列的文件

package main

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

// 我們來模擬一個(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/"):]
        // 打開文件
        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")
    }
}

這里面主要注意一下我們對錯(cuò)誤的處理. 都是直接打出panic. 這樣是很不友好的.

如果頁面輸入的文件路徑不對, 則直接404

按照之前第二步說的, 我們應(yīng)該對panic進(jìn)行處理. 比如打開文件的操作, 我們改為如下

// 打開文件
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ò)誤輸出出來. 我們希望經(jīng)過包裝后輸出錯(cuò)誤

于是做了如下修改.

第一步: 將http.handleFunc中的函數(shù)部分提出來, 這部分是業(yè)務(wù)邏輯.

提出來以后做了如下修改. 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/"):]
    // 打開文件
    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
}

第二: 封裝錯(cuò)誤內(nèi)容

這里就體現(xiàn)了函數(shù)式編程的特點(diǎn), 靈活

// 定義一個(gè)函數(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的錯(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)用的部分

// 我們來模擬一個(gè)web服務(wù)器. 在url上輸入一個(gè)地址, 然后顯示文件內(nèi)容
// 做一個(gè)顯示文件的web server
func main() {
    http.HandleFunc("/list/", WrapHandler(fileListener.FileHandler))

    // 監(jiān)聽端口:8888
    err := http.ListenAndServe(":8888", nil)
    if  err != nil {
        panic("err")
    }
}

這樣, 當(dāng)我們再次輸入錯(cuò)誤的文件路徑時(shí), 提示信息如下:

四、panic

發(fā)生panic的時(shí)候, 會(huì)做那些事呢?

1. 停止當(dāng)前函數(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("未知錯(cuò)誤:%v", r))
        }
    }()
    panic(errors.New("錯(cuò)誤"))

}

func main() {
    tryRecover()
}

六、error vs panic

七、錯(cuò)誤處理綜合示例

第五條的案例, 我們進(jìn)行了error的統(tǒng)一管理, 但是還沒有對其他異常進(jìn)行recover, 還有可能導(dǎo)致程序崩潰. 比如http://localhost:8888/abc. 繼續(xù)優(yōu)化代碼.

這樣很不友好, 我們在看看控制臺, 發(fā)現(xiàn)程序并沒有掛掉, 這是為什么呢? 想象一下, 應(yīng)該是程序自動(dòng)給我們r(jià)ecover了.

我們來看看server.go

原來server.go已經(jīng)幫我們r(jià)ecover了, recover后并不是中斷進(jìn)程, 而是打印輸出錯(cuò)誤日志. 雖然如此, 但頁面顯示依然很難看. 因此我們要做兩件事

1. 如果出現(xiàn)異常, 我們自己進(jìn)行recover, 那么他就不會(huì)走系統(tǒng)定義的recover了. 這還不夠, 這只是說控制臺不會(huì)再打印出一大堆藍(lán)色異常代碼了. 我們還有做第二件事

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ā)生錯(cuò)誤")
                http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            }
        }()

        // 執(zhí)行原來的邏輯. 然后增加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)
        }
    }
}

這樣異常就被我們捕獲了, 頁面打印出

這樣就好看多了. 我們在對代碼進(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開頭")
    }
    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)這個(gè)打印的還是系統(tǒng)給出的錯(cuò)誤異常. 那么,我們有沒有辦法, 把這個(gè)異常打印出來呢?

if c := strings.Index(request.URL.Path, "/list/"); c != 0 {
    return errors.New("url 不是已list開頭")
}

我們自己來定義一個(gè)異常處理的接口

type userError interface {
    error        // 系統(tǒng)異常
    Message() string    // 用戶自定義異常
}

接口定義好了, 在哪里用呢? 你想打印出自己的異常信息, 那就不能打印系統(tǒng)的. 自定義信息在系統(tǒng)異常之前判斷

// 執(zhí)行原來的邏輯. 然后增加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)
}

接下來是具體實(shí)現(xiàn)了, 現(xiàn)在用戶想要實(shí)現(xiàn)自定義一個(gè)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
}

這樣一個(gè)實(shí)現(xiàn)自定義打印異常的功能就做好了. 異常也是可以封裝的.

最后再來梳理這個(gè)小案例:

1. 我們有一個(gè)想法, 模擬web請求, 在瀏覽器url上輸入一個(gè)文件路徑, 打印文件的內(nèi)容

2. 內(nèi)容可能有錯(cuò)誤, 進(jìn)行異常處理.

3. 有時(shí)候異常拋出的是系統(tǒng)給出, 我們自己對異常進(jìn)行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)

        //自定義異常處理

        // 錯(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請求
    http.HandleFunc("/", WrapError(handling.Hanldering))

    // 指定服務(wù)端口
    http.ListenAndServe(":8888", nil)
}

以上就是詳解Go語言的錯(cuò)誤處理和資源管理的詳細(xì)內(nèi)容,更多關(guān)于Go 錯(cuò)誤處理 資源管理的資料請關(guān)注腳本之家其它相關(guān)文章!

您可能感興趣的文章:
  • 詳解Go多協(xié)程并發(fā)環(huán)境下的錯(cuò)誤處理
  • 關(guān)于Mongodb參數(shù)說明與常見錯(cuò)誤處理的總結(jié)
  • Golang巧用defer進(jìn)行錯(cuò)誤處理的方法
  • Go語言中更優(yōu)雅的錯(cuò)誤處理
  • GO語言標(biāo)準(zhǔn)錯(cuò)誤處理機(jī)制error用法實(shí)例
  • Django靜態(tài)資源部署404問題解決方案
  • Django跨域資源共享問題(推薦)
  • 基于Django靜態(tài)資源部署404的解決方法
  • Django靜態(tài)資源URL STATIC_ROOT的配置方法

標(biāo)簽:崇左 蘭州 銅川 湘潭 仙桃 衡水 湖南 黃山

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳解Go語言的錯(cuò)誤處理和資源管理》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266