go語言優(yōu)雅地處理error工具及技巧詳解
引言
我看到很多 golang 社區(qū)的開發(fā)者,特別是因為它的簡單性而被吸引的開發(fā)者,對 golang 中的事情應該如何處理做出了一些快速的判斷。
其中一件事就是錯誤處理。由于目前大多數語言的開發(fā)者都來自于 OOP 背景,他們習慣于處理異常,或者說"拋出"異常的概念來停止當前的應用程序的流程,而且他們大多認為這也是 golang 的方式,我們必須在出錯的情況下停止我們的應用程序的流程。他們錯了!
不要濫用你的工具
我見過很多,我以前也是這樣做的。每當有意外情況發(fā)生時,就用 os.exit(1),然后繼續(xù)前進。好吧,這不是使用go的正確方法!
我明白為什么這被廣泛使用,因為大多數早期的 golang 應用程序只是終端工具,而且許多這些工具曾經使用 .sh 可執(zhí)行文件來構建,我們曾經只是退出1;以表示剛剛發(fā)生了一些意外的事情,我們想退出。
我們把這種習慣帶到了我們的 golang 簡單的終端應用中,然后又帶到了復雜的應用中,這只是另一種 Cargo Cult Programming。 我高度鼓勵你在不得不這樣做的情況下,要非常小心,因為它是:
- 隨著你的應用程序的增長,非常難以維護。
- 最重要的是,不可能對這樣的代碼進行單元測試,這顯然表明它的不潔性。
- 以這種方式退出將阻止你的任何延遲操作的執(zhí)行,你的程序將立即終止,這可能導致資源泄漏。請考慮一下這個例子:
func main() {
dbConnection := db.connect("...")
defer dbConnection.Close() // this operation won't be executed!
entity := Entity{}
err := dbConnection.Save(entity)
if err != nil {
os.Exit(1)
}
}
考慮傳遞你的錯誤
錯誤只是 golang 中的另一種類型,你必須用它們來控制程序的執(zhí)行流程。
為了做到這一點,我們必須在整個程序中傳播這些錯誤,直到適當的處理點。
考慮一個管理訂單的HTTP API,我們想禁止客戶在特定條件下下訂單,例如:
package order
// package errors
var (
UnableToShipToCountry = errors.New("unable to ship order to the requested country")
)
type Order struct {
// ... order fields
}
type OrderRepo struct {
DB db
// ...
}
func newOrderFromRequest(o OrderRequest) (Order, error) {
if o.ShippingAddress.Country != "DE" {
return UnableToShipToCountry
} // ... the creation logic
return Order{...}, nil
}
func (r *OrderRepo)PlaceOrder(o OrderRequest) error {
order, err := newOrderFromRequest(o)
if err != nil {
// don't handle the error here, its handling may differ
return err
} // ... db transaction may also return an error
return r.db.Save(order)
}
在我們的 http package 中:
package http
http.HandleFunc("/order", func (w http.ResponseWriter, r *http.Request) {
orderRequest := createOrderRequest(r)
err := orderRepo.PlaceOrder(orderRequest)
if errors.Is(err, order.UnableToShipToCountry) {
w.WriteHeader(http.StatusBadRequest)
return
}
if err != nil {
// this error in case of DB transaction failure
w.WriteHeader(http.StatusInternalServerError)
return
} // ...
w.WriteHeader(http.StatusOK)
})
定制你的錯誤
我們可以創(chuàng)建我們自己的自定義錯誤值,并在我們的程序中使用它,同時考慮添加一些有用的信息,如錯誤跟蹤這可能會給我們的日志增加一個有益的價值,特別是在調試期間。
type AppErr struct {
msg string
code int
trace string
}
func (e AppErr) Error() string {
return fmt.Sprintf("Msg: %s, code: %d, trace:\n %s", e.msg, e.code, e.trace)
}
func NewAppErr(msg string, code int) AppErr {
stackSlice := make([]byte, 512)
s := runtime.Stack(stackSlice, false)
return AppErr{msg, code, fmt.Sprintf("\n%s", stackSlice[0:s])}
}
而我們在一個包內有這樣一個用例:
package admin
func A() error {
return b()
}
func b() error {
return NewAppErr("error from b function!", 3)
}
main.go:
func main() {
err := admin.A()
fmt.Println(err)
}
記錄的錯誤信息將是:
Msg: error from b function!, code: 3, trace:
goroutine 1 [running]:
./cmd/app/error.NewAppErr({0x1f42b0, 0x17}, 0x7)
./cmd/app/error/error.go:16 +0x35
./cmd/app/admin.b(...)
./cmd/app/admin/**admin.go:12**
./cmd/app/admin.A(...)
./cmd/app/admin/**admin.go:8**
main.main()
./cmd/app/**main.go:10** +0x8d
你也可以考慮在生產環(huán)境中關閉你的跟蹤打印,或者通過檢查其他配置值。
以上就是go語言優(yōu)雅地處理error工具及技巧詳解的詳細內容,更多關于go語言處理error工具的資料請關注腳本之家其它相關文章!

