第七节 Go错误处理与测试

 

go的错误处理、测试、文档

与其说是教程,更像是总结

一起学习:smiley:


错误处理

error内置接口

package main

import (
   "errors"
   "fmt"
   "math"
)


func Sqrt(f float64) (float64, error) {
   if f < 0 {
      return 0, errors.New("同学,这是负数呀!")
   } else {
      return math.Sqrt(f), nil
   }
}

func main() {
   //异常情况
   res := math.Sqrt(-100)
   fmt.Println(res) //NaN,也就是Not-a-Number,非数,不是数。NaN不是一个非数,而是一类非数。

   //使用自己定义的异常错误,直接打印错误信息
   res, err := Sqrt(-100)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println(res)
   }

   //err.Error(),输出错误信息
   res, err = Sqrt(-10)
   if err != nil {
      fmt.Println(err.Error())
   } else {
      fmt.Println(res)
   }
}
NaN
同学,这是负数呀!
同学,这是负数呀!

error对象

创建error对象

  1. errors.New
err1 := errors.New("XXX")
fmt.Println(err1.Error())
fmt.Println(err1)
  1. Errorf 本质还是errors.New(xxx)
err2 := fmt.Errorf("XXX%d",1)
fmt.Println(err2.Error())
fmt.Println(err2)

Errorf源码

// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand. It is
// invalid to include more than one %w verb or to supply it with an operand
// that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
func Errorf(format string, a ...interface{}) error {
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)
	s := string(p.buf)
	var err error
	if p.wrappedErr == nil {
		err = errors.New(s)
	} else {
		err = &wrapError{s, p.wrappedErr}
	}
	p.free()
	return err
}

自定义错误

自定义错误结构体

type MyError struct {
   When time.Time
   What string
}

实现Error()接口

func (e MyError) Error() string {
   return fmt.Sprintf("%v : %v", e.When, e.What)
}

测试函数

func Sqrt(f float64) (float64, error) {
   errorInfo := ""
   if f < 0 {
      errorInfo = fmt.Sprintf("不能为负数 %v ", f)
   }

   if errorInfo != "" {
      return 0, MyError{time.Now(), errorInfo}
   } else {
      return math.Sqrt(f), nil
   }
}

main入口

func main() {
   sqrt, err := Sqrt(-100)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println(sqrt)
   }
}
2021-10-14 16:13:17.6144098 +0800 CST m=+0.003169801 : 不能为负数 -100 

defer

  • 确保调用在函数结束时发生
  • 参数在defer语句时计算
  • defer列表为后进先出
/**
1.defer 会在函数结束前输出,return,panic执行之前
2.如果多出defer.会按照栈排列输出,先进后出
*/

func tryDefer() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	fmt.Println(3)
}

func main() {
	tryDefer()
}
3
2
1

利用defei实现字符串倒序

func main() {
   str := "利用defer实现字符串倒序"
   fmt.Println(str)
   ReverseString(str)
}

func ReverseString(str string) {
   for _, c := range []rune(str) {
      defer fmt.Printf("%c", c)
   }
}
利用defer实现字符串倒序
序倒串符字现实refed用利

panic

最严重的错误,程序会直接结束(如果没有recover)

Recover(宕机恢复)

panic 和 recover 的组合有如下特性:

  • 有 panic 没 recover,程序宕机。
  • 有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。

recover需要在defer中使用

func testFunc(){
	defer func(){
		msg := recover();
		fmt.Println("获取recover的返回值: ",msg)
	}()
	fmt.Println("panic 前")
	panic("调用panic")
}
panic 前
获取recover的返回值: 调用panic

错误案例

/**
  @Go version: 1.17.6
  @project: TeachCode
  @ide: GoLand
  @file: errorTest.go
  @author: Lido
  @time: 2022-02-20 22:55
  @description:
*/
package main

import (
	"errors"
	"fmt"
)

func f1(arg int) (int, error) {
	if arg == 42 {

		return -1, errors.New("can't work with 42")

	}

	return arg + 3, nil
}

type argError struct {
	arg  int
	prob string
}

func (e argError) Error() string {
	return fmt.Sprintf("%d - %s", e.arg, e.prob)
}

func f2(arg int) (int, error) {
	if arg == 42 {
		return -1, argError{arg, "can't work with it"}
	}
	return arg + 3, nil
}

func main() {

	for _, i := range []int{7, 42} {
		if r, e := f1(i); e != nil {
			fmt.Println("f1 failed:", e)
		} else {
			fmt.Println("f1 worked:", r)
		}
	}
	for _, i := range []int{7, 42} {
		if r, e := f2(i); e != nil {
			fmt.Println("f2 failed:", e)
		} else {
			fmt.Println("f2 worked:", r)
		}
	}

	_, e := f2(42)
	/*
	   如果你想在程序中使用自定义错误类型的数据,
	   你需要通过类型断言来得到这个自定义错误类型的实例。
	*/
	if ae, ok := e.(argError); ok {
		fmt.Println(ae.arg)
		fmt.Println(ae.prob)
	}
}
f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it

代码测试

不要调式,要好的测试

需要测试的函数

func calcTriangle(a, b int) int {
	var c int
	c = int(math.Sqrt(float64(a*a + b*b)))
	return c
}

创建测试文件

必须要以xxx_test.go的格式 !

traingle_test.go

测试函数

测试函数必须以TestXxxx开头 !

package main

import "testing"

func Test_Triangle(t *testing.T) {
	tests := []struct{ a, b, c int }{
		{3, 4, 5},
		{5, 12, 13},
		{8, 15, 17},
		{12, 35, 37},
		{30000, 40000, 50000},
	}

	for _, tt := range tests {
		if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
			t.Errorf("calcTriangle(%d, %d); "+
				"got %d; expected %d",
				tt.a, tt.b, actual, tt.c)
		}
	}
}

允许测试

//先cd 到文件目录下
go test .

代码覆盖率

//生成检测结果
go test -coverprofile=c.out

//查看检测结果
go tool cover -html=c.out

代码性能测试

func BenchmarkSubStr(b *testing.B) {
	a := 3
	b := 4
	ans := 5

	for i := 0; i < b.N; i++ {
		actual := calcTriangle(a,b)
		if actual != ans {
			b.Errorf("got %d for input %s; "+
				"expected %d",
				actual, s, ans)
		}
	}
}
go test -bench .

检查数组数值异常

go run -race xxx.go

GO语言文档和示例代码

工具 go doc

go doc xxx

go doc Queue

工具 godoc

如果没有,安装

go get golang.org/x/tools/cmd/godoc

使用 -http 来查看文档

godoc -http :6060

编写文档示例

1.创建测试文件

2.编写测试函数

package queue

import (
	"fmt"
)

func ExampleQueue_Pop() {
	q := Queue{1}

	q.Push(2)
	q.Push(3)
	fmt.Println(q.Pop())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())

	q.Push("abc")
	fmt.Println(q.Pop())

	// Output:
	// 1
	// 2
	// false
	// 3
	// true
	// abc
}

再次打开文档

godoc -http :6060