一鞍匾、資源管理
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 處理器示姿。
代碼結構如下:
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)
}
}