Golang異常處理-panic與recover
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
在程序設計中,容錯是相當重要的一部分工作,在 Go中它是通過錯誤處理來實現的,error 雖然只是一個接口,但是其變化卻可以有很多,我們可以根據自己的需求來實現不同的處理。任何時候當你需要一個新的錯誤類型,都可以用 errors (必須先 import)包的 errors.New 函數接收合適的錯誤信息來創建。
一.自定義的一個錯誤類型
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "errors" 12 "fmt" 13 ) 14 15 var ( 16 CustomError error //用於定義錯誤的變量。 17 PromptInformation string //用於定義提示信息的變量。 18 ) 19 20 func main() { 21 PromptInformation = "這是自定義的一個錯誤類型!" 22 CustomError = errors.New(PromptInformation) //errors包的New方法就可以創建一個error類型的數據,不過他需要你傳入一個字符串類型用於給用戶的提示信息。 23 fmt.Printf("error: %v", CustomError) 24 } 25 26 27 28 #以上代碼執行結果如下: 29 error: 這是自定義的一個錯誤類型!
二.調用自定義的錯誤
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "errors" 12 "fmt" 13 ) 14 15 type Customer struct { 16 Name string 17 Deposit float64 18 } 19 20 func CustomError()error { //這是我們自己定義的錯誤 21 return errors.New("對不起,您的余額已不足。") 22 } 23 24 func TransferAccounts(name1, name2 Customer,money float64) (Customer,Customer,error) { //這個函數是用來實現轉賬的功能。 25 if name1.Deposit - money < 0 { 26 return name1,name2, CustomError() 27 }else { 28 name1.Deposit = name1.Deposit - money 29 name2.Deposit = name2.Deposit + money 30 } 31 return name1,name2, nil 32 } 33 34 func main() { 35 yzj := Customer{"尹正傑",1000000} 36 Linus := Customer{"林納斯·托瓦茲",100} 37 38 name1,name2,err := TransferAccounts(yzj,Linus,50000) //如果在賬戶余額充足的情況下,是不會報錯的,我們讓他輸入兩個人各自的余額。 39 if err != nil { 40 fmt.Println(err) 41 }else { 42 fmt.Println(name1,name2) 43 } 44 45 name1,name2,err = TransferAccounts(yzj,Linus,600000000000000) //注意,這是第二次調轉賬啦。這回我們故意把轉賬的金額寫的遠遠大於存款。就會拋出我們定義的錯誤。 46 if err != nil { 47 fmt.Println(err) 48 }else { 49 fmt.Println(name1,name2) 50 } 51 } 52 53 54 55 #以上代碼執行結果如下: 56 {尹正傑 950000} {林納斯·托瓦茲 50100} 57 對不起,您的余額已不足。
三.用 fmt 創建錯誤對象
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 ) 13 14 type Customer struct { 15 Name string 16 Deposit float64 17 } 18 19 func TransferAccounts(name1, name2 Customer,money float64) (Customer,Customer,error) { //這個函數是用來實現轉賬的功能。 20 if name1.Deposit - money < 0 { 21 return name1,name2, fmt.Errorf("對不起,【%s】的用戶余額已不足!",name1.Name) //通常你想要返回包含錯誤參數的更有信息量的字符串就可以用可以用 fmt.Errorf() 來實現。 22 }else { 23 name1.Deposit = name1.Deposit - money 24 name2.Deposit = name2.Deposit + money 25 } 26 return name1,name2, nil 27 } 28 29 func main() { 30 yzj := Customer{"尹正傑",1000000} 31 Linus := Customer{"林納斯·托瓦茲",100} 32 33 name1,name2,err := TransferAccounts(yzj,Linus,50000) //如果在賬戶余額充足的情況下,是不會報錯的,我們讓他輸入兩個人各自的余額。 34 if err != nil { 35 fmt.Println(err) 36 }else { 37 fmt.Println(name1,name2) 38 } 39 40 name1,name2,err = TransferAccounts(yzj,Linus,600000000000000) //注意,這是第二次調轉賬啦。這回我們故意把轉賬的金額寫的遠遠大於存款。就會拋出我們定義的錯誤。 41 if err != nil { 42 fmt.Println(err) 43 }else { 44 fmt.Println(name1,name2) 45 } 46 } 47 48 49 50 #以上代碼執行結果如下: 51 {尹正傑 950000} {林納斯·托瓦茲 50100} 52 對不起,【尹正傑】的用戶余額已不足!
四.運行時異常和 panic
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 9 package main 10 11 import ( 12 "bufio" 13 "os" 14 "fmt" 15 ) 16 17 var ( 18 String string 19 Input string 20 ) 21 22 func main() { 23 f := bufio.NewReader(os.Stdin) //讀取輸入的內容 24 for { 25 fmt.Print("請輸入您的用戶名>") 26 Input,_ = f.ReadString('\n') //定義一行輸入的內容分隔符。 27 if len(Input) == 1 { 28 continue //如果用戶輸入的是一個空行就讓用戶繼續輸入。 29 } 30 fmt.Printf("您輸入的是:%s",Input) 31 fmt.Sscan(Input,&String) 32 if String == "stop" { 33 break 34 } 35 if String == "yinzhengjie" { 36 fmt.Println("歡迎登陸!") 37 }else { 38 panic("您輸入的用戶不存在!") //該程序要求用戶輸入一個字符串,一旦輸入的字符串不是“yinzhengjie”就讓程序直接崩潰掉。 39 } 40 } 41 } 42 43 44 45 #以上代碼執行結果如下: 46 請輸入您的用戶名>yinzhengjie 47 您輸入的是:yinzhengjie 48 歡迎登陸! 49 請輸入您的用戶名>linus 50 您輸入的是:linus 51 panic: 您輸入的用戶不存在! 52 53 goroutine 1 [running]: 54 main.main() 55 E:/Code/Golang/Golang_Program/錯誤處理/4.運行時異常和 panic.go:38 +0x3b1 56 57 Process f
五.從 panic 中恢復(Recover)
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 ) 13 14 func badCall() { //定義一個讓程序運行時崩潰的函數 15 panic("bad end") 16 } 17 18 func test() { 19 defer func() { 20 if e := recover(); e != nil { 21 fmt.Printf("Panicing %s\n", e) //我們知道這個程序已經拋出了panic的錯誤了,但是我們用recover函數是可以處理這個錯誤的,我們這里的做法就是打印這個錯誤的輸出並且不讓程序崩潰。 22 } 23 }() 24 badCall() //調用這個運行時崩潰的函數,因此下面的一行代碼是不會被執行的,而是直接結束當前函數,而結束函數之后就會觸發defer關鍵字,因此會被recover函數捕捉。 25 fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt 26 } 27 28 func main() { 29 fmt.Printf("Calling test\r\n") 30 test() //調用我們定義的函數,發現程序並沒有崩潰,而是可以繼續執行下一行代碼的喲! 31 fmt.Printf("Test completed\r\n") 32 } 33 34 35 36 37 #以上代碼執行結果如下: 38 Calling test 39 Panicing bad end 40 Test completed
六.自定義包中的錯誤處理和 panicking
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import ( 11 "fmt" 12 "strings" 13 "strconv" 14 ) 15 16 17 type ParseError struct { //定義一個處理錯誤的結構體 18 key int 19 Value string 20 Err error 21 } 22 23 func (p *ParseError) String() string { //給ParseError定義一個String()方法。 24 return fmt.Sprintf(" [%q] type is not int!" , p.Value) 25 } 26 27 28 func JudgmentType(fields []string) (numbers []int) { //這個函數是判斷fields切片中的每個元素是否都可以轉換成INT類型的。 29 if len(fields) == 0 { 30 panic("Nothing can be explained!") 31 } 32 for key, value := range fields { 33 num, err := strconv.Atoi(value) //這里是講每一個切片元素中的字符串進行轉換。 34 if err != nil { 35 panic(&ParseError{key, value, err}) //如果解析出錯就將自定義的ParseError結構體的error對象返回。 36 } 37 numbers = append(numbers, num) //如果轉換成int類型沒有出錯的話就會被追加到一個提前定義好的切片中。 38 } 39 return //我們這里可以寫numbers,說白了只要寫一個[]int類型的都可以,當然,如果你不寫的話默認就會返回我們提前定義好的numbers喲。 40 } 41 42 43 func StringParse(input string) (numbers []int, err error) { //這個函數是用來解析字符串的。 44 defer func() { //用recover函數來接受panic拋出的錯誤信息。 45 if ErrorOutput := recover(); ErrorOutput != nil { 46 var ok bool 47 err, ok = ErrorOutput.(error) //很顯然,這里是一種斷言操作,即判斷是否有error類型出現。 48 if !ok { 49 err = fmt.Errorf("Parse error: %v", ErrorOutput) 50 } 51 } 52 }() 53 fields := strings.Fields(input) 54 numbers = JudgmentType(fields) 55 return 56 } 57 58 func main() { 59 var yzj = []string{ 60 "100 200 300", 61 "1 2 2.5 3", 62 "30 * 40", 63 "yinzhengjie Golang", 64 "", 65 } 66 67 for _, ex := range yzj { 68 fmt.Printf("正在解析[ %q]:\n ", ex) 69 result, err := StringParse(ex) 70 if err != nil { 71 fmt.Println("解析結果:",err) 72 continue 73 } 74 fmt.Println("解析結果:",result) 75 } 76 } 77 78 79 80 #以上代碼執行結果如下: 81 正在解析[ "100 200 300"]: 82 解析結果: [100 200 300] 83 正在解析[ "1 2 2.5 3"]: 84 解析結果: Parse error: ["2.5"] type is not int! 85 正在解析[ "30 * 40"]: 86 解析結果: Parse error: ["*"] type is not int! 87 正在解析[ "yinzhengjie Golang"]: 88 解析結果: Parse error: ["yinzhengjie"] type is not int! 89 正在解析[ ""]: 90 解析結果: Parse error: Nothing can be explained!
七.一種用閉包處理錯誤的模式
1 /* 2 #!/usr/bin/env gorun 3 @author :yinzhengjie 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/ 5 EMAIL:y1053419035@qq.com 6 */ 7 8 package main 9 10 import "fmt" 11 12 func A() { 13 defer func() { 14 if r := recover(); r != nil { 15 fmt.Println("Recovered in A", r) 16 } 17 }() 18 fmt.Println("Calling A.") 19 B(0) 20 fmt.Println("Returned normally from g.") 21 } 22 23 func B(i int) { 24 if i > 3 { 25 fmt.Println("Panicking!") 26 panic(fmt.Sprintf("%v", i)) 27 } 28 defer fmt.Println("Defer in B", i) 29 fmt.Println("Printing in B", i) 30 B(i + 1) //這里是一個遞歸函數函數。 31 } 32 33 func main() { 34 A() 35 fmt.Println("程序結束!") 36 } 37 38 39 40 #以上地面執行結果如下: 41 Calling A. 42 Printing in B 0 43 Printing in B 1 44 Printing in B 2 45 Printing in B 3 46 Panicking! 47 Defer in B 3 48 Defer in B 2 49 Defer in B 1 50 Defer in B 0 51 Recovered in A 4 52 程序結束!
