测试文档编写效果

12/29/2023 tag0

# 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。
// 错误值可以存储在变量中,从函数中返回,传递参数 等等。
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

自定义错误

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)

}
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
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")
}
1
2
3
4
5
6
7
8
9

img

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")
}
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

运行结果:

img

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")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

img

  • 在 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")
}
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

img

执行逻辑:

1、panic 触发

2、触发panic,当前函数的所有defer语句,倒序执行

3、直到遇到recover处理了这个panic..函数结束,继续倒序执行

4、main,继续向下执行。