异常处理

目录
  1. 1. 1.介绍
  2. 2. 2. error接口
    1. 2.1. 2.1 语法
    2. 2.2. 2.2 函数返回错误
    3. 2.3. 2.3 判断错误
    4. 2.4. 2.4 创建error对象几种方式
  3. 3. 3.自定义错误
    1. 3.1. 3.1 自定义错误的实现步骤
    2. 3.2. 3.2 使用示例
  4. 4. 4.延迟函数(defer)
    1. 4.1. 4.1 概念
    2. 4.2. 4.2 在函数中使用
  5. 5. 5.panic(崩溃)
    1. 5.1. 5.1 造成panic的场景
      1. 5.1.1. 1.数组访问越界
      2. 5.1.2. 2.访问未初始化的指针或 nil 指针
      3. 5.1.3. 3.向已经 close 的 chan(管道) 里发送数据
      4. 5.1.4. 4.类型断言
    2. 5.2. 5.2 使用
  6. 6. 6.recover(恢复)
    1. 6.1. 6.1 介绍
    2. 6.2. 6.2 使用

1.介绍

错误是指程序中出现不正常的情况,从而导致程序无法正常运行。Go语言中没有try...catch来捕获错误,而是通过defer+recover+panic模式来实现捕捉错误信息。

2. error接口

2.1 语法

Go语言通过内置的错误类型提供了非常简单的错误处理机制,即error接口。该接口的定义如下:

1
2
3
type error interface {
Error() string
}

2.2 函数返回错误

对于大多数函数,如果要返回错误,大致上都可以定义为如下模式,必须将error作为多种返回值中的最后一个。

1
2
3
4
// 函数返回错误
func Demo(参数列表...)(x T, err error){
// 函数体
}

2.3 判断错误

在Go语言中处理错误的方式通常是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。如果不是nil,需打印输出错误。

1
2
3
4
x,err := Demo(参数列表...)
if err != nil {
// 打印错误信息....
}

2.4 创建error对象几种方式

常见的创建error对象的几种方式:

  • Go语言errors包下的New()函数可以返回error对象。
  • 使用fmt包下的Errorf()函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import (
"errors"
"fmt"
)
func main() {
// 方式一: 使用errors包下的New()
err := createError(1)
printError(err)
// 方式二: 使用fmt包下的Errorf()
err2 := createError(2)
printError(err2)
}
func printError(err error){
if err != nil{
fmt.Printf("err==> %v | err.Error() ==> %v | 类型==> %T \n",err,err.Error(),err)
}
}

// 创建error对象
func createError(way int) error {
if way == 1 {
// 方式一: 使用errors包下的New()
return errors.New("方式一: 使用errors包下的New() ")
} else if way == 2 {
// 方式二: 使用fmt包下的Errorf()
return fmt.Errorf("方式二: 使用fmt包下的Errorf(...) ---> ")
}
return nil
}
/**输出
err==> 方式一: 使用errors包下的New() | err.Error() ==> 方式一: 使用errors包下的New() | 类型==> *errors.errorString
err==> 方式二: 使用fmt包下的Errorf(...) ---> | err.Error() ==> 方式二: 使用fmt包下的Errorf(...) ---> | 类型==> *errors.errorString
*/

3.自定义错误

3.1 自定义错误的实现步骤

  • 定义一个结构体,表示自定义错误的类型。
  • 让自定义错误类型实现error接口:Error() string
  • 定义一个返回error的函数。根据程序实际功能而定。

3.2 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main
import (
"fmt"
"time"
)
// 第一步: 定义一个结构体
type MyError struct {
Msg string
Time time.Time
}
// 第二步: 并实现error接口
func (m MyError) Error() string {
return fmt.Sprintf("错误信息: %s 发生时间: %v", m.Msg, m.Time)
}
// 第三步: 定义一个返回error的函数
func login(phone, pwd string) (bool, error) {
if phone == "17600000111" && pwd == "123456" {
return true,nil
}
err := MyError{"账号密码错误!", time.Now()}
return false,err
}
func main() {
// 测试
res,err := login("17600000111","123789")
if err != nil {
fmt.Printf("登录失败--> %v T --> %T \n", err.Error(), err)
} else {
fmt.Printf("登录成功 --> %v \n", res)
}
res2,err2 := login("17600000111","123456")
if err2 != nil {
fmt.Printf("登录失败--> %v T --> %T \n", err2.Error(), err2)
}else {
fmt.Printf("登录成功 --> %v \n", res2)
}
}
/**输出
登录失败--> 错误信息: 账号密码错误! 发生时间: 2020-11-27 16:09:39.774176 +0800 CST m=+0.000091945 T --> main.MyError
登录成功 --> true
*/

4.延迟函数(defer)

4.1 概念

关键字defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。defer语句只能出现在函数或方法的内部。

4.2 在函数中使用

在函数中可以添加多个defer语句。如果有很多调用defer,当函数执行到最后时,这些defer语句会按照逆序执行(报错的时候也会执行),最后该函数返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import "fmt"
func main() {
// 测试
defer defer1()
defer defer2(4,6)
defer defer2(40,60)
// 匿名函数
defer func() {
fmt.Println("匿名函数defer3....")
}()
// 函数正常处理代码
for i:= 1; i<4 ; i++ {
fmt.Println(i)
}
}
// 无参数的函数
func defer1() {
fmt.Println("函数defer1....")
}
// 有参数的函数
func defer2(a,b int) {
fmt.Printf("函数defer2....a=%d b= %d a+b=%d \n",a,b,a+b)
}
/**输出
1
2
3
匿名函数defer3....
函数defer2....a=40 b= 60 a+b=100
函数defer2....a=4 b= 6 a+b=10
函数defer1....
*/

defer语句经常被用于处理成对的操作,如打开-关闭、连接-断开连接、加锁-释放锁。特别是在执行打开资源的操作时,遇到错误需要提前返回,在返回前需要关闭相应的资源,不然很容易造成资源泄露等问题。

5.panic(崩溃)

panic让当前的程序进入恐慌,中断程序的执行。panic()是一个内建函数,可以中断原有的控制流程。其功能类似于PHP中的throw

5.1 造成panic的场景

1.数组访问越界
1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
// 测试访问数组下标越界
arr := [3]int{1,2,3}
// 数组下标最大为2
fmt.Println(arr[3])
}
/** 报错
./main.go:9:17: invalid array index 3 (out of bounds for 3-element array)
*/
2.访问未初始化的指针或 nil 指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
// 测试-> 访问未初始化的指针或 nil 指针
// 1.定义一个指针类型(默认值是nil)
var b *int
fmt.Println(b)
// 2.访问nil的指针
fmt.Println(*b)
}
/** 报错
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6ef8]
*/
3.向已经 close 的 chan(管道) 里发送数据
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
func main() {
// 测试->往已经 close 的 `chan`(管道) 里发送数据
// 1.声明一个管道
var ch = make(chan int,1)
// 2.关闭管道
close(ch)
// 3.向已关闭管道写入数据
ch <- 1
}
/** 报错
panic: send on closed channel
*/
4.类型断言
1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {
// 测试->类型断言错误
var i interface{} = "hello"
a := i.([]string)
fmt.Println(a)
}
/**报错
panic: interface conversion: interface {} is string, not []string
*/

5.2 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import "fmt"
func main() {
throw("请求成功!",1)
throw("请求失败!",0)
}
func throw(msg string,code int) {
if code == 0 {
panic("报错信息: " + msg)
}
fmt.Println("正确输出:" + msg)
}
/** 输出
正确输出:请求成功!
panic: 报错信息: 请求失败!

goroutine 1 [running]:
main.throw(0x10cce7a, 0xd, 0x0)
/Users/hui/Project/Go/src/go-basic/main.go:12 +0x145
main.main()
/Users/hui/Project/Go/src/go-basic/main.go:7 +0x65

Process finished with exit code 2
*/

6.recover(恢复)

6.1 介绍

Go语言中没有try...catch来捕获错误,一旦触发panic就会导致程序崩溃。在Go中是通过recover让程序恢复。值的注意的是recover()必须在延迟函数(defer)中有效。

在正常的程序运行过程中,调用 recover()会返回 nil,并且没有其他任何效果。如果当前的Goroutine陷入panic,调用recover()可以捕获panic()的输入值,使程序恢复正常运行。

6.2 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"
func main() {
// 定义一个匿名延迟函数
defer func() {
err := recover()
msg := fmt.Sprintf("err信息: %v",err)
if err != nil {
// 程序触发panic时,会被这里捕获
fmt.Println(msg)
}
}()
// 定义一个数组
testPanic("请求失败")
}
// 故意抛出panic
func testPanic(err string) {
panic("错误信息" + err)
}
/** 输出
err信息: 错误信息请求失败
*/