go学习之异常记录01:panic: reflect: call of reflect.Value.NumField on int Value

代码:

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type Inner struct {
	Email string `validate:"email"`
}

type Outer struct {
	Age   int `validate:"eq=15"`
	Inner Inner
}

func validateEmail(email string) bool {
	return true
}

func validate(v interface{}) (bool, string) {
	validateRes := true
	errMsg := "success"

	vt := reflect.TypeOf(v)
	vv := reflect.ValueOf(v)

	// fmt.Println("vv:", vv)
	// fmt.Println("vv's kind :", vv.Kind())

	for i := 0; i < vv.NumField(); i++ {
		fieldVal := vv.Field(i)
		k := vv.Kind()
		validatestr := vt.Field(i).Tag.Get("validate")

		switch k {
		case reflect.Int:
			val := fieldVal.Int()

			// fmt.Println("int...")
			tagVal := strings.Split(validatestr, "=")
			tagValInt, _ := strconv.ParseInt(tagVal[1], 10, 64)
			if val != tagValInt {
				errMsg = "validate int failed,tag is " + strconv.FormatInt(tagValInt, 10)
				validateRes = false
			}
		case reflect.String:
			val := fieldVal.String()
			tagStr := validatestr
			switch tagStr {
			case "email":
				emailValidate := validateEmail(val)
				if !emailValidate {
					errMsg = "validate email failed ,field val is : " + val
					validateRes = false
				}
			}
		case reflect.Struct:
			// fmt.Println("access struct")
			valInter := fieldVal.Interface()
			innerRes, err := validate(valInter)
			if !innerRes {
				validateRes = innerRes
				errMsg = err
			}
		}

	}
	return validateRes, errMsg
}

func main() {
	inner := Inner{
		Email: "[email protected]",
	}
	outer := Outer{
		Age:   15,
		Inner: inner,
	}
	res, err := validate(outer)
	if !res {
		fmt.Println(err)
	}
}

这是一个校验器的实现,利用的是树的深度遍历算法,以及反射的知识。然而运行后报错如下:

panic: reflect: call of reflect.Value.NumField on int Value

goroutine 1 [running]:
reflect.flag.mustBe(0x82, 0x19)
        E:/software/develop/go/src/reflect/value.go:207 +0xbd
reflect.Value.NumField(...)
        E:/software/develop/go/src/reflect/value.go:1292
main.validate(0x4a6800, 0xc000004480, 0x82, 0x1, 0x4a6800)
        F:/go-code/src/go_advanced_pro/validate/demo01/main.go:33 +0x165
main.validate(0x4b35a0, 0xc000004480, 0x4b35a0, 0xc000004480, 0x493abb)
        F:/go-code/src/go_advanced_pro/validate/demo01/main.go:63 +0x2ee
main.main()
        F:/go-code/src/go_advanced_pro/validate/demo01/main.go:82 +0x78
exit status 2

根据报错信息可以追踪到程序第33行出错了

for i := 0; i < vv.NumField(); i++ {...

这一行出错的唯一可能就是vv.NumField()出错了。跟踪错误:ctrl+左进入mustBe()方法:

// mustBe panics if f's kind is not expected.
// Making this a method on flag instead of on Value
// (and embedding flag in Value) means that we can write
// the very clear v.mustBe(Bool) and have it compile into
// v.flag.mustBe(Bool), which will only bother to copy the
// single important word for the receiver.
func (f flag) mustBe(expected Kind) {
	if f.kind() != expected {
		panic(&ValueError{methodName(), f.kind()})
	}
}

看注释知道这是个要接收期待的kind才能成功运行的方法,于是就去看看传入这个方法的kind是什么,ctri+左进入NumField()方法。

// NumField returns the number of fields in the struct v.
// It panics if v's Kind is not Struct.
func (v Value) NumField() int {
	v.mustBe(Struct)
	tt := (*structType)(unsafe.Pointer(v.typ))
	return len(tt.fields)
}

可以看到要求传入的必须是结构体。

回过头来检查我们的代码,main方法里传入的是个结构体,但是报这个错表明递归的时候传入出错了。加上下面这句代码重新执行

 fmt.Println("vv's kind :", vv.Kind())  //去掉上面代码的注释

结果是

vv's kind : struct
vv's kind : int

可以看到递归进去后变成int了,检查代码后发现用来进行switch匹配的k 由这段代码得到

k := vv.Kind()

恍然大悟,这里不应该是vv来调用,而是应该fieldVal来调用,因为我们是根据传入结构体属性的值是什么类型来决定走哪个分支,而不是根据结构体的类型。。

总结

粗心使人进步 = =

你可能感兴趣的:(go,go,golang,bug)