俗話說得好:「神仙打鼓有時錯」,程式在執行階段當然難免會有錯誤產生。為了避免錯誤造成怎麼系統中斷或資料疑遺漏等嚴重的情況發生,合適的錯誤處理是不管哪種語言都需要考量的系統設計的一環,golang當然也不例外。golang不像是Java或是C#一樣有try...catch
異常處理機制,而是靠著函數返回值逐層上回拋。比方說我們在第十天-靜態檔案分享伺服器中的例子:
if err := http.ListenAndServe(":8000", nil); err != nil {
panic(err)
}
我們可以看到,藉由宣告一個變數err
來接住錯誤狀態,透過判斷err != nil
來做錯誤處理,在這個例子當中是將錯誤訊息列印下來。我們來看另外一個例子:
package main
import "fmt"
func main() {
defer func() {
fmt.Println("first")
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("end")
}()
f()
}
func f() {
fmt.Println("test")
panic(1)
fmt.Println("test2")
}
defer
會將程式放到一個暫存區塊,當函數要返回時才會執行,我們可以先想像為一個在函數回傳之前才會檢查待辦清單。panic
在這邊會產生一個錯誤,錯誤訊息為1
。所以上述的例子執行後,我們會得到:
test
first
1
end
這邊要特別注意的是test2
是不會執行到的,因為錯誤的關係,程式執行到end
就結束了。如果panic
是真正的錯誤,go程式會因此而關閉,除非有使用recover
回復他的數值。
關於defer有三大特性,這邊既然講到了就介紹一下,首先是「先進後出」,我們來看個簡單的例子:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
defer fmt.Println(i)
}
}
如果沒有defer,很直覺地認為結果會是:
0
1
2
3
4
但因為defer先進後出的特性,我們得到的會是:
4
3
2
1
0
第二個特性是可以修改函數中的命名返回值,這樣講可能很難理解,我們直接看例子:
func c() (i int) {
defer func() { i++ }()
return 1
}
不考慮defer的話,返回值為1
,但是因為defer會在返回時執行的特性,所以最終返回結果會是2而不是1。
第三點是defer中的變量在宣告的時候就已經被確立了,不會受到宣告以後的影響,一樣來看個例子:
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
因為宣告的時候i = 0
,所以結果就會印出0
,而不是defer宣夠以後因為i++
所以等於2。透過三個特性,有沒有更了解一點了呢?
回到錯誤處理當中,除了預先定義的錯誤訊息以外,我們當然也可以自定義錯誤訊息:
package main
import (
"fmt"
)
type MyError struct {
Title string
Message string
}
func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.Title, e.Message)
}
func main() {
err := MyError{"Error Title 1", "Error Message 1"}
fmt.Println(err)
err = MyError{
Title: "Error Title 2",
Message: "Error Message 2",
}
fmt.Println(err)
}
如何與程式中的錯誤相處,也是工程師很重要的一個修煉呢!