錯(cuò)誤
通過在函數(shù)和方法中返回錯(cuò)誤對象作為它們的唯一或最后一個(gè)返回值——如果返回 nil,則沒有錯(cuò)誤發(fā)生——并且主調(diào)(calling)函數(shù)總是應(yīng)該檢查收到的錯(cuò)誤。
處理錯(cuò)誤并且在函數(shù)發(fā)生錯(cuò)誤的地方給用戶返回錯(cuò)誤信息:照這樣處理就算真的出了問題,你的程序也能繼續(xù)運(yùn)行并且通知給用戶。panic and recover 是用來處理真正的異常
庫函數(shù)通常必須返回某種錯(cuò)誤提示給主調(diào)(calling)函數(shù)。
為了防止發(fā)生錯(cuò)誤時(shí)正在執(zhí)行的函數(shù)(如果有必要的話甚至?xí)钦麄€(gè)程序)被中止,在調(diào)用函數(shù)后必須檢查錯(cuò)誤。
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)
錯(cuò)誤處理
Go 有一個(gè)預(yù)先定義的 error 接口類型
type error interface {
Error() string
}
錯(cuò)誤值用來表示異常狀態(tài);
程序處于錯(cuò)誤狀態(tài)時(shí)可以用 os.Exit(1) 來中止運(yùn)行。
定義錯(cuò)誤
任何時(shí)候當(dāng)你需要一個(gè)新的錯(cuò)誤類型,都可以用 errors (必須先 import)包的errors.New 函數(shù)接收合適的錯(cuò)誤信息來創(chuàng)建,像下面這樣:err := errors.New(“math - square root of negative number”)在下面中你可以看到一個(gè)簡單的用例:
// 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
可以把它用于計(jì)算平方根函數(shù)的參數(shù)測試:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New (“math - square root of negative number”)
}
// implementation of Sqrt
}
你可以像下面這樣調(diào)用 Sqrt 函數(shù):
if f, err := Sqrt(-1); err != nil {
fmt.Printf(“Error: %s
”, err)
}
```## 用 fmt 創(chuàng)建錯(cuò)誤對象
通常你想要返回包含錯(cuò)誤參數(shù)的更有信息量的字符串,例如:可以用 fmt.Errorf() 來實(shí)現(xiàn):它和fmt.Printf() 完全一樣,接收有一個(gè)或多個(gè)格式占位符的格式化字符串和相應(yīng)數(shù)量的占位變量。和打印信息不同的是它用信息生成錯(cuò)誤對象。
比如在前面的平方根例子中使用:
```go
if f < 0 {
return 0, fmt.Errorf(“math: square root of negative number %g”, f)
}
第二個(gè)例子:從命令行讀取輸入時(shí),如果加了 help 標(biāo)志,我們可以用有用的信息產(chǎn)生一個(gè)錯(cuò)誤:
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
}
運(yùn)行時(shí)異常和 panic
當(dāng)發(fā)生像數(shù)組下標(biāo)越界或類型斷言失敗這樣的運(yùn)行錯(cuò)誤時(shí),Go 運(yùn)行時(shí)會(huì)觸發(fā)運(yùn)行時(shí) panic,伴隨著程序的崩潰拋出一個(gè) runtime.Error 接口類型的值。這個(gè)錯(cuò)誤值有個(gè) RuntimeError() 方法用于區(qū)別普通錯(cuò)誤。
panic 可以直接從代碼初始化:當(dāng)錯(cuò)誤條件(我們所測試的代碼)很嚴(yán)苛且不可恢復(fù),程序不能繼續(xù)運(yùn)行時(shí),可以使用 panic 函數(shù)產(chǎn)生一個(gè)中止程序的運(yùn)行時(shí)錯(cuò)誤。panic 接收一個(gè)做任意類型的參數(shù),通常是字符串,在程序死亡時(shí)被打印出來。Go 運(yùn)行時(shí)負(fù)責(zé)中止程序并給出調(diào)試信息。
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
一個(gè)檢查程序是否被已知用戶啟動(dòng)的具體例子:
var user = os.Getenv(“USER”)
func check() {
if user == “” {
panic(“Unknown user: no value for $USER”)
}
}
可以在導(dǎo)入包的 init() 函數(shù)中檢查這些。
當(dāng)發(fā)生錯(cuò)誤必須中止程序時(shí), panic 可以用于錯(cuò)誤處理模式:
if err != nil {
panic(“ERROR occurred:” + err.Error())
}
Go panicking:
在多層嵌套的函數(shù)調(diào)用中調(diào)用 panic,可以馬上中止當(dāng)前函數(shù)的執(zhí)行,所有的 defer 語句都會(huì)保證執(zhí)行并把控制權(quán)交還給接收到 panic 的函數(shù)調(diào)用者。這樣向上冒泡直到最頂層,并執(zhí)行(每層的) defer,在棧頂處程序崩潰,并在命令行中用傳給 panic 的值報(bào)告錯(cuò)誤情況:這個(gè)終止過程就是 panicking。
從 panic 中恢復(fù)(Recover)
正如名字一樣,這個(gè)(recover)內(nèi)建函數(shù)被用于從 panic 或 錯(cuò)誤場景中恢復(fù):讓程序可以從 panicking重新獲得控制權(quán),停止終止過程進(jìn)而恢復(fù)正常執(zhí)行。
recover 只能在 defer 修飾的函數(shù)中使用:用于取得 panic 調(diào)用中傳遞過來的錯(cuò)誤值,如果是正常執(zhí)行,調(diào)用 recover 會(huì)返回 nil,且沒有其它效果。
總結(jié):panic 會(huì)導(dǎo)致棧被展開直到 defer 修飾的 recover() 被調(diào)用或者程序中止。下面例子中的 protect 函數(shù)調(diào)用函數(shù)參數(shù) g 來保護(hù)調(diào)用者防止從 g 中拋出的運(yùn)行時(shí) 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 包實(shí)現(xiàn)了簡單的日志功能:默認(rèn)的 log 對象向標(biāo)準(zhǔn)錯(cuò)誤輸出中寫入并打印每條日志信息的日期和時(shí)間。除了 Println 和 Printf 函數(shù),其它的致命性函數(shù)都會(huì)在寫完日志信息后調(diào)用 os.Exit(1),那些退出函數(shù)也是如此。而 Panic 效果的函數(shù)會(huì)在寫完日志信息后調(diào)用 panic;可以在程序必須中止或發(fā)生了臨界錯(cuò)誤時(shí)使用它們.下面展示 panic,defer 和 recover 怎么結(jié)合使用的完整例子:
// 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 這樣的控制流機(jī)制。
自定義包中的錯(cuò)誤處理和 panicking
這是所有自定義包實(shí)現(xiàn)者應(yīng)該遵守的最佳實(shí)踐:
1)在包內(nèi)部,總是應(yīng)該從 panic 中 recover:不允許顯式的超出包范圍的 panic()。
2)向包的調(diào)用者返回錯(cuò)誤值(而不是 panic)。
在包內(nèi)部,特別是在非導(dǎo)出函數(shù)中有很深層次的嵌套調(diào)用時(shí),對主調(diào)函數(shù)來說用 panic 來表示應(yīng)該被翻譯成錯(cuò)誤的錯(cuò)誤場景是很有用的。
// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// ParseError 表示將單詞轉(zhuǎn)換為整數(shù)時(shí)出錯(cuò)。
type ParseError struct {
Index int // 以空格分隔的單詞列表的索引。
Word string // 生成分析錯(cuò)誤的單詞。
Err error // 引發(fā)此錯(cuò)誤的原始錯(cuò)誤(如果有)。
}
//
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse 將 put 中以空格分隔的單詞解析為整數(shù)。
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
一種用閉包處理錯(cuò)誤的模式
每當(dāng)函數(shù)返回時(shí),我們應(yīng)該檢查是否有錯(cuò)誤發(fā)生:但是這會(huì)導(dǎo)致重復(fù)乏味的代碼。結(jié)合
defer/panic/recover 機(jī)制和閉包可以得到一個(gè)我們馬上要討論的更加優(yōu)雅的模式。不過這個(gè)模式只有當(dāng)所有的函數(shù)都是同一種簽名時(shí)可用,這樣就有相當(dāng)大的限制。一個(gè)很好的使用它的例子是 web 應(yīng)用,所有的處理函數(shù)都是下面這樣:
func handler1(w http.ResponseWriter, r *http.Request) { ... }
假設(shè)所有的函數(shù)都有這樣的簽名:
func f(a type1, b type2)
參數(shù)的數(shù)量和類型是不相關(guān)的。
我們給這個(gè)類型一個(gè)名字:
fType1 = func f(a type1, b type2)
在我們的模式中使用了兩個(gè)幫助函數(shù):
1)check:這是用來檢查是否有錯(cuò)誤和 panic 發(fā)生的函數(shù):func check(err error) { if err != nil { panic(err) } }
2)errorhandler:這是一個(gè)包裝函數(shù)。接收一個(gè) fType1 類型的函數(shù) fn 并返回一個(gè)調(diào)用 fn 的函數(shù)。里面就包含有 defer/recover 機(jī)制。
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)
}
}
當(dāng)錯(cuò)誤發(fā)生時(shí)會(huì) recover 并打印在日志中;除了簡單的打印,應(yīng)用也可以用 template 包為用戶生成自定義的輸出。check() 函數(shù)會(huì)在所有的被調(diào)函數(shù)中調(diào)用,像這樣:
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)
...
}
通過這種機(jī)制,所有的錯(cuò)誤都會(huì)被 recover,并且調(diào)用函數(shù)后的錯(cuò)誤檢查代碼也被簡化為調(diào)用 check(err)即可。在這種模式下,不同的錯(cuò)誤處理必須對應(yīng)不同的函數(shù)類型;
審核編輯:黃飛
-
程序
+關(guān)注
關(guān)注
117文章
3836瀏覽量
84735 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4405瀏覽量
66792 -
Printf
+關(guān)注
關(guān)注
0文章
84瀏覽量
14619
原文標(biāo)題:一種用閉包處理錯(cuò)誤的模式
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
嵌入式編程錯(cuò)誤處理機(jī)制設(shè)計(jì)
嵌入式系統(tǒng)C語言編程中主要的錯(cuò)誤處理方式
Rust語言中錯(cuò)誤處理的機(jī)制
嵌入式C編程常用的異常錯(cuò)誤處理
labviEW錯(cuò)誤處理的問題
LabVIEW錯(cuò)誤處理問題
AF錯(cuò)誤處理
LabVIEW中的錯(cuò)誤處理
Spring Boot框架錯(cuò)誤處理
嵌入式系統(tǒng)C語言編程中的錯(cuò)誤處理資料總結(jié)
Rust代碼啟發(fā)之返回值異常錯(cuò)誤處理

閉包在錯(cuò)誤處理中的應(yīng)用模式探索
評論