测试文档编写效果
# 2.1 错误
错误:指的是程序中预期会发生的结果,预料之中
打开一个文件:文件正在被占用,可知的。
异常:不该出现问题的地方出现了问题,预料之外
调用一个对象,发现这个对象是个空指针对象,发生错误。
错误是业务的一部分,而异常不是。
package main
import (
"fmt"
"os"
)
// 错误是开发中必须要思考的问题
// - 某些系统错误 ,文件被占用,网络有延迟
// - 人为错误:核心就是一些不正常的用户会怎么来给你传递参数,sql注入
func main() {
//打开一个文件 os 系统包,所有可以用鼠标和键盘能执行的事件,都可以用程序实现
// func Open(name string) (*File, error)
file, err := os.Open("aaa.txt")
// 在开发中,我们需要思考这个错误的类型 PathError
// 1、文件不存在 err
// 2、文件被占用 err
// 3、文件被损耗 err
// 调用方法后,出现错误,需要解决
if err != nil {
fmt.Println(err)
return
}
fmt.Println(file.Name())
}
// 在实际工程项目中,
// 我们希望通过程序的错误信息快速定位问题,但是又不喜欢错误处理代码写的冗余而又啰嗦。
// Go语言没有提供像Java. c#语言中的try...catch异常处理方式,
// 而是通过函数返回值逐层往上抛, 如果没有人处理这个错误,程序就终止 panic
// 这种设计,鼓励工程师在代码中显式的检查错误,而非忽略错误。
// 好处就是避免漏掉本应处理的错误。但是带来一个弊端,让代码繁琐。
// Go中的错误也是一种类型。错误用内置的error类型表示。就像其他类型的,如int, float64。
// 错误值可以存储在变量中,从函数中返回,传递参数 等等。
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
自定义错误
package main
import (
"errors"
"fmt"
)
// 自己定义一个错误
// 1、errors.New("xxxxx")
// 2、fmt.Errorf()
// 都会返回 error 对象, 本身也是一个类型
func main() {
age_err := setAge(-1)
if age_err != nil {
fmt.Println(age_err)
}
fmt.Printf("%T\n", age_err) // *errors.errorString
// 方式二
errInfo1 := fmt.Errorf("我是一个错误信息:%d\n", 500)
//errInfo2 := fmt.Errorf("我是一个错误信息:%d\n", 404)
if errInfo1 != nil {
// 处理这个错误
fmt.Println(errInfo1)
}
}
// 设置年龄的函数,一定需要处理一些非正常用户的请求
// 返回值为 error 类型
// 作为一个开发需要不断思考的事情,代码的健壮性和安全性
func setAge(age int) error {
if age < 0 {
// 给出一个默认值
age = 3
// 抛出一个错误 errors 包
return errors.New("年龄不合法")
}
// 程序正常的结果,给这个用户赋值
fmt.Println("年龄设置成功:age=", age)
return nil
}
package main
import (
"fmt"
)
type KuangShenError struct {
msg string
code int
}
// Error() string
func (e *KuangShenError) Error() string {
// fmt.Sprintf() 返回string
return fmt.Sprintf("错误信息:%s,错误代码:%d\n", e.msg, e.code)
//fmt.Sprintf()并不会打印到标准输出,而是返回一个字符串。如果需要将格式化后的字符串输出到标准输出,可以使用 fmt.Printf() 函数
}
// 处理error的逻辑
func (e KuangShenError) print() bool {
fmt.Println("hello,world")
return true
}
// 使用错误测试
func test(i int) (int, error) {
// 需要编写的错误
if i != 0 {
// 更多的时候我们会使用自定义类型的错误
return i, &KuangShenError{msg: "非预期数据", code: 500}
}
// 正常结果
return i, nil
}
func main() {
i, err := test(1)
if err != nil {
fmt.Println(err)
ks_err, ok := err.(*KuangShenError)
if ok {
if ks_err.print() {
// 处理这个错误中的子错误的逻辑
}
fmt.Println(ks_err.msg)
fmt.Println(ks_err.code)
}
}
fmt.Println(i)
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
ks_err, ok := err.(*KuangShenError)这行代码具体介绍一下啊
这行代码是将变量err强制类型转换为*KuangShenError类型,并将结果分别赋值给ks_err和ok变量。
Go语言中,类型断言(type assertion)可以用于将一个接口类型的值转换为另一个接口类型或具体的类型。这里使用的是类型断言的语法形式:err.(*KuangShenError)。
如果err确实是*KuangShenError类型,那么ks_err就是err的值,ok为true;否则,ks_err为nil,ok为false。
通过这种方式,可以检查错误是否是自定义的KuangShenError类型,并在必要时对其进行处理,比如调用print()方法处理错误的逻辑。
在之前的代码中没有直接调用KuangShenError类型定义的Error()方法。
但是,在代码中fmt.Println(err)会隐式调用err变量的Error()方法。因为fmt.Println()函数在输出一个自定义类型的值时,会自动调用该类型的String()方法(如果定义了的话),或者Error()方法(如果没有定义String()方法的话)。
因此,当我们在fmt.Println(err)语句中打印错误信息时,实际上是调用了err变量的Error()方法。这个方法返回的字符串会被fmt.Println()函数输出到标准输出中。
# 2.2 异常
error 往往是能预知的错误,但是也可能出现一些不可预知的错误,例如数组越界,这种错误可能会导致程序非正常退出,在 Go 语言中称之为 panic。
func get(index int) int {
arr := [3]int{2, 3, 4}
return arr[index]
}
func main() {
fmt.Println(get(5))
fmt.Println("finished")
}
2
3
4
5
6
7
8
9
package main
import "fmt"
// panic recover
//
// 出现了panic之后,如果有defer语句,先执行所有的defer语句。
//
// defer : 延迟函数,倒序执行,处理一些问题。
func main() {
defer fmt.Println("main--1")
defer fmt.Println("main--2")
fmt.Println("main--3")
testPanic(1) // 外部函数,也不会继续再向下执行了
defer fmt.Println("main--4")
fmt.Println("main--5")
}
func testPanic(num int) {
defer fmt.Println("testPanic--1")
defer fmt.Println("testPanic--2")
fmt.Println("testPanic--3")
// 如果在函数中一旦触发了 panic,会终止后面要执行的代码。
// 如果存在defer,正常按照defer逻辑执行
if num == 1 {
panic("出现预定的异常----panic")
}
defer fmt.Println("testPanic--4")
fmt.Println("testPanic--5")
}
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
运行结果:
recover结合defer处理 panic 恐慌
在 Python、Java 等语言中有 try...catch 机制,在 try 中捕获各种类型的异常,在 catch 中定义异常处理的行为。Go 语言也提供了类似的机制 defer 和 recover。
package main
import "fmt"
func get1(index int) (ret int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Some error happened!", r)
ret = -1
}
}()
arr := [3]int{2, 3, 4}
return arr[index]
}
func main() {
fmt.Println(get1(5))
fmt.Println("finished")
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 在 get 函数中,使用 defer 定义了异常处理的函数,在协程退出前,会执行完 defer 挂载的任务。因此如果触发了 panic,控制权就交给了 defer。
- 在 defer 的处理逻辑中,使用 recover,使程序恢复正常,并且将返回值设置为 -1,在这里也可以不处理返回值,如果不处理返回值,返回值将被置为默认值 0。
package main
import "fmt"
// panic recover
//
// 出现了panic之后,如果有defer语句,先执行所有的defer语句。
//
// defer : 延迟函数,倒序执行,处理一些问题。
func main() {
defer fmt.Println("main--1")
defer fmt.Println("main--2")
fmt.Println("main--3")
testPanic1(1) // 外部函数,如果在函数内部已经处理panic,那么程序会继续执行
defer fmt.Println("main--4")
fmt.Println("main--5")
}
func testPanic1(num int) {
// 出去函数的时候处理这里面可能发生的panic
// recover func recover() any 返回panic传递的值
// panic func panic(v any)
defer fmt.Println("0000")
defer func() {
if msg := recover(); msg != nil {
fmt.Println("recover执行了... panic msg:", msg)
// 处理逻辑
fmt.Println("---------程序已恢复----------")
}
}()
defer fmt.Println("testPanic--1")
defer fmt.Println("testPanic--2")
fmt.Println("testPanic--3")
// 如果在函数中一旦触发了 panic,会终止后面要执行的代码。
// 如果存在defer,正常按照defer逻辑执行
if num == 1 {
panic("出现预定的异常----panic")
}
defer fmt.Println("testPanic--4")
fmt.Println("testPanic--5")
}
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
执行逻辑:
1、panic 触发
2、触发panic,当前函数的所有defer语句,倒序执行
3、直到遇到recover处理了这个panic..函数结束,继续倒序执行
4、main,继续向下执行。