錯誤
通過在函數和方法中返回錯誤對象作為它們的唯一或最后一個返回值——如果返回 nil,則沒有錯誤發生——并且主調(calling)函數總是應該檢查收到的錯誤。
處理錯誤并且在函數發生錯誤的地方給用戶返回錯誤信息:照這樣處理就算真的出了問題,你的程序也能繼續運行并且通知給用戶。panic and recover 是用來處理真正的異常
庫函數通常必須返回某種錯誤提示給主調(calling)函數。
為了防止發生錯誤時正在執行的函數(如果有必要的話甚至會是整個程序)被中止,在調用函數后必須檢查錯誤。
if value, err := pack1.Func1(param1); err != nil { fmt.Printf(“Error %s in pack1.Func1 with parameter %v”, err.Error(), param1) return // or: return err } // Process(value)
錯誤處理
Go 有一個預先定義的 error 接口類型
type error interface { Error() string }
錯誤值用來表示異常狀態;
程序處于錯誤狀態時可以用 os.Exit(1) 來中止運行。
定義錯誤
任何時候當你需要一個新的錯誤類型,都可以用 errors (必須先 import)包的errors.New 函數接收合適的錯誤信息來創建,像下面這樣:err := errors.New(“math - square root of negative number”)在下面中你可以看到一個簡單的用例:
// errors.go package main import ( "errors" "fmt" ) var errNotFound error = errors.New("Not found error") func main() { fmt.Printf("error: %v", errNotFound) } // error: Not found error
可以把它用于計算平方根函數的參數測試:
func Sqrt(f float64) (float64, error) { if f < 0 { return 0, errors.New (“math - square root of negative number”) } // implementation of Sqrt }
你可以像下面這樣調用 Sqrt 函數:
if f, err := Sqrt(-1); err != nil { fmt.Printf(“Error: %s ”, err) } ```## 用 fmt 創建錯誤對象 通常你想要返回包含錯誤參數的更有信息量的字符串,例如:可以用 fmt.Errorf() 來實現:它和fmt.Printf() 完全一樣,接收有一個或多個格式占位符的格式化字符串和相應數量的占位變量。和打印信息不同的是它用信息生成錯誤對象。 比如在前面的平方根例子中使用: ```go if f < 0 { return 0, fmt.Errorf(“math: square root of negative number %g”, f) }
第二個例子:從命令行讀取輸入時,如果加了 help 標志,我們可以用有用的信息產生一個錯誤:
if len(os.Args) > 1 && (os.Args[1] == “-h” || os.Args[1] == “--help”) { err = fmt.Errorf(“usage: %s infile.txt outfile.txt”, filepath.Base(os.Args[0])) return }
運行時異常和 panic
當發生像數組下標越界或類型斷言失敗這樣的運行錯誤時,Go 運行時會觸發運行時 panic,伴隨著程序的崩潰拋出一個 runtime.Error 接口類型的值。這個錯誤值有個 RuntimeError() 方法用于區別普通錯誤。
panic 可以直接從代碼初始化:當錯誤條件(我們所測試的代碼)很嚴苛且不可恢復,程序不能繼續運行時,可以使用 panic 函數產生一個中止程序的運行時錯誤。panic 接收一個做任意類型的參數,通常是字符串,在程序死亡時被打印出來。Go 運行時負責中止程序并給出調試信息。
package main import "fmt" func main() { fmt.Println("Starting the program") panic("A severe error occurred: stopping the program!") fmt.Println("Ending the program") }
輸出如下:
Starting the program panic: A severe error occurred: stopping the program! panic PC=0x4f3038 runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032 runtime.panic(0x442938, 0x4f08e8) main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8 main.main() runtime.mainstart+0xf 386/asm.s:84 runtime.mainstart() runtime.goexit /go/src/pkg/runtime/proc.c:148 runtime.goexit() ---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed ---- Program exited with code -1073741783
一個檢查程序是否被已知用戶啟動的具體例子:
var user = os.Getenv(“USER”) func check() { if user == “” { panic(“Unknown user: no value for $USER”) } }
可以在導入包的 init() 函數中檢查這些。
當發生錯誤必須中止程序時, panic 可以用于錯誤處理模式:
if err != nil { panic(“ERROR occurred:” + err.Error()) }
Go panicking:
在多層嵌套的函數調用中調用 panic,可以馬上中止當前函數的執行,所有的 defer 語句都會保證執行并把控制權交還給接收到 panic 的函數調用者。這樣向上冒泡直到最頂層,并執行(每層的) defer,在棧頂處程序崩潰,并在命令行中用傳給 panic 的值報告錯誤情況:這個終止過程就是 panicking。
從 panic 中恢復(Recover)
正如名字一樣,這個(recover)內建函數被用于從 panic 或 錯誤場景中恢復:讓程序可以從 panicking重新獲得控制權,停止終止過程進而恢復正常執行。
recover 只能在 defer 修飾的函數中使用:用于取得 panic 調用中傳遞過來的錯誤值,如果是正常執行,調用 recover 會返回 nil,且沒有其它效果。
總結:panic 會導致棧被展開直到 defer 修飾的 recover() 被調用或者程序中止。下面例子中的 protect 函數調用函數參數 g 來保護調用者防止從 g 中拋出的運行時 panic,并展示 panic中的信息:
func protect(g func()) { defer func() { log.Println(“done”) // Println executes normally even if there is a panic if err := recover(); err != nil { log.Printf(“run time panic: %v”, err) } }() log.Println(“start”) g() // possible runtime-error }
log 包實現了簡單的日志功能:默認的 log 對象向標準錯誤輸出中寫入并打印每條日志信息的日期和時間。除了 Println 和 Printf 函數,其它的致命性函數都會在寫完日志信息后調用 os.Exit(1),那些退出函數也是如此。而 Panic 效果的函數會在寫完日志信息后調用 panic;可以在程序必須中止或發生了臨界錯誤時使用它們.下面展示 panic,defer 和 recover 怎么結合使用的完整例子:
// panic_recover.go package main import ( "fmt" ) func badCall() { panic("bad end") } func test() { defer func() { if e := recover(); e != nil { fmt.Printf("Panicing %s ", e) } }() badCall() fmt.Printf("After bad call ") // <-- wordt niet bereikt } func main() { fmt.Printf("Calling test ") test() fmt.Printf("Test completed ") }
輸出:
Calling test
Panicing bad end
Test completed
defer-panic-recover 在某種意義上也是一種像 if , for 這樣的控制流機制。
自定義包中的錯誤處理和 panicking
這是所有自定義包實現者應該遵守的最佳實踐:
1)在包內部,總是應該從 panic 中 recover:不允許顯式的超出包范圍的 panic()。
2)向包的調用者返回錯誤值(而不是 panic)。
在包內部,特別是在非導出函數中有很深層次的嵌套調用時,對主調函數來說用 panic 來表示應該被翻譯成錯誤的錯誤場景是很有用的。
// parse.go package parse import ( "fmt" "strings" "strconv" ) // ParseError 表示將單詞轉換為整數時出錯。 type ParseError struct { Index int // 以空格分隔的單詞列表的索引。 Word string // 生成分析錯誤的單詞。 Err error // 引發此錯誤的原始錯誤(如果有)。 } // func (e *ParseError) String() string { return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word) } // Parse 將 put 中以空格分隔的單詞解析為整數。 func Parse(input string) (numbers []int, err error) { defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("pkg: %v", r) } } }() fields := strings.Fields(input) numbers = fields2numbers(fields) return } func fields2numbers(fields []string) (numbers []int) { if len(fields) == 0 { panic("no words to parse") } for idx, field := range fields { num, err := strconv.Atoi(field) if err != nil { panic(&ParseError{idx, field, err}) } numbers = append(numbers, num) } return } // panic_package.go package main import ( "fmt" "./parse/parse" ) func main() { var examples = []string{ "1 2 3 4 5", "100 50 25 12.5 6.25", "2 + 2 = 4", "1st class", "", } for _, ex := range examples { fmt.Printf("Parsing %q: ", ex) nums, err := parse.Parse(ex) if err != nil { fmt.Println(err) continue } fmt.Println(nums) } }
輸出:
Parsing “1 2 3 4 5”:
[1 2 3 4 5]
Parsing “100 50 25 12.5 6.25”:
pkg parse: error parsing “12.5” as int
Parsing “2 + 2 = 4”:
pkg parse: error parsing “+” as int
Parsing “1st class”:
pkg parse: error parsing “1st” as int
Parsing “”:
pkg: no words to parse
一種用閉包處理錯誤的模式
每當函數返回時,我們應該檢查是否有錯誤發生:但是這會導致重復乏味的代碼。結合
defer/panic/recover 機制和閉包可以得到一個我們馬上要討論的更加優雅的模式。不過這個模式只有當所有的函數都是同一種簽名時可用,這樣就有相當大的限制。一個很好的使用它的例子是 web 應用,所有的處理函數都是下面這樣:
func handler1(w http.ResponseWriter, r *http.Request) { ... }
假設所有的函數都有這樣的簽名:
func f(a type1, b type2)
參數的數量和類型是不相關的。
我們給這個類型一個名字:
fType1 = func f(a type1, b type2)
在我們的模式中使用了兩個幫助函數:
1)check:這是用來檢查是否有錯誤和 panic 發生的函數:func check(err error) { if err != nil { panic(err) } }
2)errorhandler:這是一個包裝函數。接收一個 fType1 類型的函數 fn 并返回一個調用 fn 的函數。里面就包含有 defer/recover 機制。
func errorHandler(fn fType1) fType1 { return func(a type1, b type2) { defer func() { if e, ok := recover().(error); ok { log.Printf(“run time panic: %v”, err) } }() fn(a, b) } }
當錯誤發生時會 recover 并打印在日志中;除了簡單的打印,應用也可以用 template 包為用戶生成自定義的輸出。check() 函數會在所有的被調函數中調用,像這樣:
func f1(a type1, b type2) { ... f, _, err := // call function/method check(err) t, err := // call function/method check(err) _, err2 := // call function/method check(err2) ... }
通過這種機制,所有的錯誤都會被 recover,并且調用函數后的錯誤檢查代碼也被簡化為調用 check(err)即可。在這種模式下,不同的錯誤處理必須對應不同的函數類型;
審核編輯:黃飛
-
程序
+關注
關注
117文章
3817瀏覽量
82208 -
函數
+關注
關注
3文章
4365瀏覽量
63908 -
Printf
+關注
關注
0文章
83瀏覽量
14029
原文標題:一種用閉包處理錯誤的模式
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Rust語言中錯誤處理的機制
嵌入式C編程常用的異常錯誤處理
LabVIEW錯誤處理問題
LabVIEW中的錯誤處理
Spring Boot框架錯誤處理
Rust代碼啟發之返回值異常錯誤處理

評論