source: funnyZpc
在 Java 世界最習慣的模式就是 try-finally and try-catch-finally ,主要的用途有三種:
- try-catch 用來處理例外
- 到了1.7 版 更可以直接用來處理 AutoClosable 的 resource
- finally 就是用來處理一堆雜七雜八最後處理的東西,包含
- 經過 try-catch 處理後也出錯的錯誤處理
- 或者是一些無法 AutoClosable 的 resource
但是到了 Go 的世界這兩件事被拆開來了,分別是 error handling and delay handling
Error handling
首先最讓 Java 開發者最不習慣的 error handling ,每個 function 都是透過回傳 error 來處理,於是就會有一堆處理 error 的 if 到處出現,舉例如下:
uid, err := ts.ValidateAuthToken(ctx, token) if err != nil { ts.logger.Errorf("ValidateAuthToken err %+v.\n", err) return fmt.Errorf("authToken not found") }
然後就這樣一路往上丟,丟到有人要處理為止..... (默默的告訴自己要習慣....)
Illustration created for “A Journey With Go”, made from the original Go Gopher
created by Renee French.
delay handling - defer
Defer 有點像 finally,會確保一個 function call 執行結束前執行,主要會被用來執行一些清理工作(比如說清理資源,關閉資源),不過 Go 的世界跟 Java 的世界最大的差異就是不能無腦亂用這些東西(咦?),所以在使用前必須先瞭解 defer 的特性:
- defer 它是以類似 Stack 的方式執行,也就是在 function 執行結束後按照呼叫的順序反相執行,可以參考『Inlined defers in Go』,在這篇文章裡舉了一個很有實際的例子,讓我們可以很清楚 defer 的行為模式就是類似 Stack 的方,下面的 function 內執行兩個 defer function
0: func run() { 1: defer foo() 2: defer bar() 3: 4: fmt.Println("hello") 5: }
而實際上執行起來的順序如下:
runtime.deferproc(foo) // generated for line 1 runtime.deferproc(bar) // generated for line 2 // Other code... runtime.deferreturn(bar) // generated for line 5 runtime.deferreturn(foo) // generated for line 5
- 即使 function 發生 嚴重錯誤也會被執行,這邊跟 java 的 finally 一樣
- 支持 anonymous function 的呼叫,所以代表可以延遲執行某個 function
下面這些則是跟 Java 界差異比較大的特性(包含 pointer & lock)
- 常用於清理資源、文件關閉、解鎖(unlock)以及記錄時間等善後操作(解鎖就是大雷區,這我也還在熟悉中...)
- 如果 function 內某個變數作為 defer 時 anonymous function 的參數,則在定義 defer 就拷貝一份(類似 call by value ),否則則是引用某個變數的位址(call by referecne)
Panic handling 也就是程式 crash 後的處理
懷念被 try catch 保護的日子~~(淚)
- Go 的異常機制需要更多人為介入,不像 JVM 就算收到一堆 uncatch runtime exception 程式都還能運作,想要能睡好覺,就必須要研究一下透過 panic/recover 模式來處理錯誤。 Update:
- panic 可以在任何地方引發,但 recover 只有在 defer 調用的函數中有效
這時候不得不說 Java 攻城獅真的被照顧得很好,就算程式邏輯有錯誤,都還能相對安穩的睡覺等到隔天再起來 Debug(並不會,還是得盡快處理...Orz..), 但是 Go 如果發生任何 panic 事件(代表無法被 error 處理),系統就直接死給你看,就算使用了 recover,如果 recover 的 defer function 沒寫好,系統還是無限循環死給你看....
最近遇到的案例就是使用 Bufflao 的 ORM ,因為我們在處理 Dao 的部分沒有寫好,所以系統一直發生 panic ,產生下面讓我這種新手很難理解的錯誤訊息:
reflect: call of reflect.Value.FieldByName on ptr Value
好死不死 Bufflao 內建有 recover 機制,一直把把這個錯誤攔截掉,並且透過 recover 重啟系統,所以系統還能順利運行,但是卻又沒有 release DB connection,所以之前使用過的資源就一直卡住,在 recover 幾次後系統就 deadlock 在那邊完全沒反應,害我們 debug 找超久,程式如下:
// PanicHandler recovers from panics gracefully and calls // the error handling code for a 500 error. func (a *App) PanicHandler(next Handler) Handler { return func(c Context) error { defer func() { //catch or finally r := recover() var err error if r != nil { //catch switch t := r.(type) { case error: err = t case string: err = fmt.Errorf(t) default: err = fmt.Errorf(fmt.Sprint(t)) } err = err events.EmitError(events.ErrPanic, err, map[string]interface{}{ "context": c, "app": a, }, ) eh := a.ErrorHandlers.Get(http.StatusInternalServerError) eh(http.StatusInternalServerError, err, c) } }() return next(c) } }
這算是一個標準範例,當 http server 攔截到 500 (internal server error)的 panic 錯誤,就會自動進行 revcover 的動作,但是我猜測在這邊沒有看到 release db connection,所以很快就會把 Go 內建的 connection pool 吃光光而造成 deadlock。
也有看到許多相關的討論(對新手來說這邊雷很多...):
- [reddit] How do you handle db/cache connections and errors in a web project?
- 5 Gotchas of Defer in Go — Part I
- Do we need to close DB connection before closing application in Go
(因為我也還在研究)
沒有留言 :
張貼留言