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
	t   time.Time
}
// 第二步: 并实现error接口
func (m MyError) Error() string {
	return fmt.Sprintf("错误信息: %s 发生时间: %v", m.msg, m.t)
}
// 第三步: 定义一个返回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陷入恐慌,调用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
23
package main
import "fmt"
func main() {
	// 定义一个匿名延迟函数
	defer func() {
		err := recover()
		msg := fmt.Sprintf("err信息: %v",err)
		if err != nil {
			// 程序触发panic时,会被这里捕获
			fmt.Println(msg)
		}
	}()
	// 定义一个数组
	testPanic("请求失败")
	fmt.Println("panic后面的执行流程")
}
// 故意抛出panic
func testPanic(err string)  {
	panic("错误信息" + err)
}
/** 输出
  err信息: 错误信息请求失败
*/