Go 語言極速入門7 - 資源管理與錯誤處理

一鞍匾、資源管理

Java 有 try-finally镰矿,可以在 finally 中進行資源的關閉;Go 可以使用 defer

  • defer 在函數結束時發(fā)生調用
  • defer 的調用是棧類型 - 先進后出
  • defer 通常用于資源關閉 Open/Close穗椅,Lock/UnLock 等

一句話總結:defer 的調用機制是 “將defer語句加入棧中蔓倍,當函數結束時(包括正常執(zhí)行結束 / return / panic 出錯結束等),從棧中依次執(zhí)行 defer

func writeFile(filename string) {
    file, err := os.Create(filename)
    if err != nil {
        panic(err)
    }
    defer file.Close() // 將 "file.Close()" 壓入 defer 棧中

    writer := bufio.NewWriter(file)
    defer writer.Flush() // 將 "writer.Flush()" 壓入 defer 棧中

    fmt.Fprintln(writer, "123")
    // 當函數執(zhí)行結束時敞咧,從 defer 棧中執(zhí)行語句 - 后進先出棘捣,先 "writer.Flush()",再 "file.Close()"
}

func main() {
    writeFile("defer.txt")
}

二休建、錯誤簡單處理

使用機制

  • 通過被調用函數的注釋查看其可能發(fā)生的錯誤乍恐,然后依據錯誤類型并進行處理;
  • 錯誤處理結束后要 return
    file, err := os.Open(filename)
    // 錯誤處理
    if err != nil {
        // 判斷 err 是否是 *os.PathError测砂,因為 os.Open(filename) 的注釋:"If there is an error, it will be of type *PathError."
        if pathError, ok := err.(*os.PathError); ok {
            fmt.Printf("error: %s", pathError.Error())
        } else {
            fmt.Printf("notKnown error:%s", err.Error())
        }
        return // 返回
    }

error 是一個接口茵烈,定義如下:

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

基于該接口我們可以實現(xiàn)自定義的 error 實現(xiàn)。( errors 是 error 接口的一個實現(xiàn)砌些,可以直接仿照呜投,也可以直接使用,如 err := errors.New("my custom error")

三存璃、panic & recover

panic

  • 停止當前程序運行
  • 一直向上返回仑荐,執(zhí)行每一層的 defer
  • 如果沒有遇見 recover,程序退出

recover(相當于對 panic 的 catch 語句)

  • 僅在 defer 調用中使用
  • 獲取 panic 的值
  • 如果無法處理纵东,可重新 panic
import (
    "fmt"
    "errors"
)

func recove() {
    defer func() {
        // func recover() interface{}粘招,表示 recover() 函數的返回類型可以是各種類型,所以要判斷是否是 error
        // 使用 recover() catch panic偎球,防止程序直接退出
        r := recover()
        if err, ok := r.(error); ok {
            fmt.Println(err) // runtime error: integer divide by zero
        } else {
            panic(errors.New("not known error"))
        }
    }()

    b := 0
    a := 5/b // panic: runtime error: integer divide by zero
    fmt.Println(a)

    //panic("123") // panic: not known error
}

func main() {
    recove()
}

四男图、錯誤統(tǒng)一處理

一個生產系統(tǒng)通常包含兩種異常

  • 不可直接暴露給用戶的異常:例如系統(tǒng)內部異常
  • 需要暴露給用戶的異常:例如部分自定義異常信息用于提示用戶操作

本節(jié)寫一個需求:實現(xiàn)一個讀取文件的 httpServer 處理器示姿。
代碼結構如下:


image.png

userError 自定義用戶異常接口

package exception

type UserError interface {
    error // 內嵌類型
    Message() string
}

myCustomError 自定義用戶異常實現(xiàn)

package exception

// 基于基本類型創(chuàng)建自定義類型
type MyCustomError string

func (e MyCustomError) Error() string {
    return e.Message()
}

func (e MyCustomError) Message() string {
    return string(e)
}

handler 核心業(yè)務邏輯處理器

package handler

import (
    "net/http"
    "os"
    "io/ioutil"
    "strings"
    "github.com/zhaojigang/helloworld/filelisting/exception"
)

const PathPrefix = "/list/"

// 實現(xiàn)一個讀取文件的 httpServer 處理器
// 假設訪問 http://localhost:8888/list/abc.txt
func HandleFileListing(writer http.ResponseWriter, request *http.Request) error {
    // 1. 如果 urlPath 不是以 /list/ 開頭的,則自定義用戶錯誤
    if strings.Index(request.URL.Path, PathPrefix) != 0 {
        return exception.MyCustomError("url path need startWith /list/")
    }
    //fmt.Println("path", request.URL.Path)    // /list/abc.txt
    path := request.URL.Path[len(PathPrefix):] // abc.txt 字符串切割逊笆,subString

    // 2. 打開文件
    file, err := os.Open(path)
    if err != nil {
        // 遇到錯誤直接返回栈戳,由錯誤統(tǒng)一處理器進行處理
        return err
    }
    defer file.Close()

    // 3. 讀取文件到 byte[]
    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    // 4. 將 byte[] all 寫出到響應流
    writer.Write(all)
    return nil
}

errorWrapperHandler 統(tǒng)一錯誤處理器

package exception

import (
    "net/http"
    "log"
    "os"
)

// 定義一個 function 類型的 type,返回值是 error
type appHandler func(writer http.ResponseWriter, request *http.Request) error

// 輸入 appHandler 是一個函數难裆,輸出也是一個函數 - 函數式編程
func ErrWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        // 1. 處理業(yè)務邏輯
        err := handler(writer, request)
        if err != nil {
            log.Printf("error occured, %s", err) // 2018/11/04 10:10:12 error occured, open abc.txt1: no such file or directory

            // 2. 處理可以拋給用戶的錯誤
            if err, ok := err.(UserError); ok {
                // 將錯誤寫回到 http.ResponseWriter
                http.Error(writer, err.Message(), http.StatusBadRequest)
            }

            // 3. 處理不可以拋給用戶的錯誤
            code := http.StatusOK
            switch {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            default:
                code = http.StatusInternalServerError
            }
            http.Error(writer, http.StatusText(code), code) // 瀏覽器:Not Found
        }
    }
}

注意這樣的姿勢:定義一個 function 類型的 type子檀,返回值是 error

  • type appHandler func(xx) error

web httpServer 服務器

package main

import (
    "net/http"
    "github.com/zhaojigang/helloworld/filelisting/handler"
    "github.com/zhaojigang/helloworld/filelisting/exception"
)

func main() {
    // 1. 注冊處理 handler.PathPrefix 開頭的業(yè)務邏輯處理器
    http.HandleFunc(handler.PathPrefix, exception.ErrWrapper(handler.HandleFileListing))

    // 2. 啟動 httpServer,監(jiān)聽端口
    err := http.ListenAndServe("127.0.0.1:8888", nil)

    // 3. 如果啟動失敗乃戈,則直接拋出錯誤
    if err != nil {
        panic(err)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末褂痰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子症虑,更是在濱河造成了極大的恐慌缩歪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谍憔,死亡現(xiàn)場離奇詭異匪蝙,居然都是意外死亡,警方通過查閱死者的電腦和手機习贫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門逛球,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苫昌,你說我怎么就攤上這事颤绕。” “怎么了祟身?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵奥务,是天一觀的道長。 經常有香客問我袜硫,道長汗洒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任父款,我火速辦了婚禮,結果婚禮上瞻凤,老公的妹妹穿的比我還像新娘憨攒。我一直安慰自己,他們只是感情好阀参,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布肝集。 她就那樣靜靜地躺著,像睡著了一般蛛壳。 火紅的嫁衣襯著肌膚如雪杏瞻。 梳的紋絲不亂的頭發(fā)上所刀,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音捞挥,去河邊找鬼浮创。 笑死,一個胖子當著我的面吹牛砌函,可吹牛的內容都是我干的斩披。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼讹俊,長吁一口氣:“原來是場噩夢啊……” “哼垦沉!你這毒婦竟也來了?” 一聲冷哼從身側響起仍劈,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厕倍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贩疙,有當地人在樹林里發(fā)現(xiàn)了一具尸體讹弯,經...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年屋群,在試婚紗的時候發(fā)現(xiàn)自己被綠了闸婴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡芍躏,死狀恐怖邪乍,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情对竣,我是刑警寧澤庇楞,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站否纬,受9級特大地震影響吕晌,放射性物質發(fā)生泄漏。R本人自食惡果不足惜临燃,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一睛驳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膜廊,春花似錦乏沸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铆铆,卻和暖如春蝶缀,著一層夾襖步出監(jiān)牢的瞬間丹喻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工翁都, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碍论,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓荐吵,卻偏偏與公主長得像骑冗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子先煎,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內容