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 string17 Deposit float6418 }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 - money29 name2.Deposit = name2.Deposit + money30 }31 return name1,name2, nil32 }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 string16 Deposit float6417 }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 - money24 name2.Deposit = name2.Deposit + money25 }26 return name1,name2, nil27 }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 main10 11 import (12 "bufio"13 "os"14 "fmt"15 )16 17 var (18 String string19 Input string20 )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 break34 }35 if String == "yinzhengjie" {36 fmt.Println("欢迎登陆!")37 }else {38 panic("您输入的用户不存在!") //该程序要求用户输入一个字符串,一旦输入的字符串不是“yinzhengjie”就让程序直接崩溃掉。39 }40 }41 }42 43 44 45 #以上代码执行结果如下:46 请输入您的用户名>yinzhengjie47 您输入的是:yinzhengjie48 欢迎登陆!49 请输入您的用户名>linus50 您输入的是:linus51 panic: 您输入的用户不存在!52 53 goroutine 1 [running]:54 main.main()55 E:/Code/Golang/Golang_Program/错误处理/4.运行时异常和 panic.go:38 +0x3b156 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 bereikt26 }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 test39 Panicing bad end40 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 int19 Value string20 Err error21 }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 bool47 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 return56 }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 continue73 }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 043 Printing in B 144 Printing in B 245 Printing in B 346 Panicking!47 Defer in B 348 Defer in B 249 Defer in B 150 Defer in B 051 Recovered in A 452 程序结束!