Pay attention, do not get lost in dry  goods👆


First, let's warm up with a simple code logic. What do you think the following code should print?
type OKR struct {   id      int   content string}
func getOkrDetail(ctx context.Context, okrId int) (*OKR, *okrErr.OkrErr) { return &OKR{id: okrId, content: fmt.Sprint(rand.Int63())}, nil}
func getOkrDetailV2(ctx context.Context, okrId int) (*OKR, okrErr.OkrError) { if okrId == 2{ return nil, okrErr.OKRNotFoundError } return &OKR{id: okrId, content: fmt.Sprint(rand.Int63())}, nil}
func paperOkrId(ctx context.Context) (int, error){ return 1, nil}
func Test001(ctx context.Context) () { var okr *OKR okrId, err := paperOkrId(ctx) if err != nil{ fmt.Println("#### 111 ####") } okr, err = getOkrDetail(ctx, okrId) if err != nil { fmt.Println("#### 222 ####") } okr, err = getOkrDetailV2(ctx, okrId) if err != nil { fmt.Println("#### 333 ####") }
okr, err = getOkrDetailV2(ctx, okrId + 1) if err != nil { fmt.Println("#### 444 ####") } fmt.Println("#### 555 ####") fmt.Printf("%v", okr)}
func main() { Test001(context.Background())}
picture

foreword

Before talking about reflection, let's take a look at some principles of Golang's type design
  • Variables in Golang include (type, value) two parts
  • Understanding this will solve the simple problem above
  • Type includes static type and concrete type. In short, static type is the type you see in coding (such as int, string), and concrete type is the type that the runtime system sees. The success of type assertion depends on the concrete type of the variable, not the static type.
The next thing to talk about is reflection, which is the ability to update variables and check the value of variables at runtime, call methods on variables, and intrinsic operations supported by variables, without knowing the specific types of these variables at compile time. This mechanism is called reflection. The basic type of Golang is static (that is, specifying variables such as int and string, and its type is static type), which is determined when the variable is created. Reflection is mainly related to the interface type of Golang (its type is concrete type ), only the runtime interface type has reflection.
Why use reflection in Golang / what scenarios can (should) use reflection

When the program runs, we get an interface variable. How should the program know the type of the current variable and the value of the current variable?

Of course we can have pre-defined specified types, but if there is a scenario where we need to write a function that can handle a type of common logic, but there are many input types, or we don't know what the type of the received parameter is, or maybe There is no agreement; it may also be that there are many types passed in, and these types cannot be represented uniformly. At this time, reflection will be used, a typical example is: json.Marshal.

For another example, sometimes it is necessary to decide which function to call based on certain conditions, such as deciding based on user input. At this time, it is necessary to reflect on the function and the parameters of the function, and execute the function dynamically during the runtime.

Example scene:

For example, we need to perform a certain operation on a struct (replace with formatted printing). In this scenario, we can achieve it in many ways. The simpler way is: switch case  

func Sprint(x interface{}) string {    type stringer interface {        String() string    }    switch x := x.(type) {    case stringer:        return x.String()    case string:        return x    case int:        return strconv.Itoa(x)    // int16, uint32...    case bool:        if x {            return "true"        }        return "false"    default:        return "wrong parameter type"    }}
type permissionType int64
But there is a problem with this simple method. When adding a scene, such as the need for slice support, you need to add a branch. This increase is endless, whenever I need to support a type, even if it is custom The type, which is essentially int64 also still needs to add a fork.

Basic usage of reflection

Golang provides us with two methods, reflect.ValueOf and reflect.TypeOf, which can help us get the value and type of the object respectively. Valueof returns the Reflect.Value object, which is a struct, while typeof returns Reflect.Type which is an interface. We only need to simply use the combination of these two to complete a variety of functions.
type GetOkrDetailResp struct {   OkrId   int64   UInfo   *UserInfo   ObjList []*ObjInfo}type ObjInfo struct {   ObjId int64   Content string}
type UserInfo struct { Name string Age int IsLeader bool Salary float64 privateFiled int}
// 利用反射创建structfunc NewUserInfoByReflect(req interface{})*UserInfo{ if req == nil{ return nil } reqType :=reflect.TypeOf(req) if reqType.Kind() == reflect.Ptr{ reqType = reqType.Elem() } return reflect.New(reqType).Interface().(*UserInfo)}
// 修改struct 字段值func ModifyOkrDetailRespData(req interface{}) { reqValue :=reflect.ValueOf(req).Elem() fmt.Println(reqValue.CanSet()) uType := reqValue.FieldByName("UInfo").Type().Elem() fmt.Println(uType) uInfo := reflect.New(uType) reqValue.FieldByName("UInfo").Set(uInfo)}
// 读取 struct 字段值,并根据条件进行过滤func FilterOkrRespData(reqData interface{}, objId int64){// 首先获取req中obj slice 的valuefor i := 0 ; i < reflect.ValueOf(reqData).Elem().NumField(); i++{ fieldValue := reflect.ValueOf(reqData).Elem().Field(i)if fieldValue.Kind() != reflect.Slice{continue } fieldType := fieldValue.Type() // []*ObjInfo sliceType := fieldType.Elem() // *ObjInfo slicePtr := reflect.New(reflect.SliceOf(sliceType)) // 创建一个指向 slice 的指针 slice := slicePtr.Elem() slice.Set(reflect.MakeSlice(reflect.SliceOf(sliceType), 0, 0)) // 将这个指针指向新创建slice// 过滤所有objId == 当前objId 的structfor i := 0 ;i < fieldValue.Len(); i++{if fieldValue.Index(i).Elem().FieldByName("ObjId").Int() != objId {continue } slice = reflect.Append(slice, fieldValue.Index(i)) }// 将resp 的当前字段设置为过滤后的slice fieldValue.Set(slice) }}
func Test003(){// 利用反射创建一个新的对象var uInfo *UserInfo uInfo = NewUserInfoByReflect(uInfo) uInfo = NewUserInfoByReflect((*UserInfo)(nil))
// 修改resp 返回值里面的 user info 字段(初始化) reqData1 := new(GetOkrDetailResp) fmt.Println(reqData1.UInfo) ModifyOkrDetailRespData(reqData1) fmt.Println(reqData1.UInfo)
// 构建请求参数 reqData := &GetOkrDetailResp{OkrId: 123} for i := 0; i < 10; i++{ reqData.ObjList = append(reqData.ObjList, &ObjInfo{ObjId: int64(i), Content: fmt.Sprint(i)}) }// 输出过滤前结果 fmt.Println(reqData)// 对respData进行过滤操作 FilterOkrRespData(reqData, 6)// 输出过滤后结果 fmt.Println(reqData)}

Performance Analysis and Advantages and Disadvantages of Reflection

Everyone has heard more or less that reflection performance is low. Using reflection is several times to dozens of times lower than normal calls. I don’t know if you have thought about the aspects of low reflection performance. Let me make a simple analysis first. , When acquiring or modifying the content of a value through reflection, there are several more memory references and a few more detours, and it is definitely not quick to directly call a certain value. This is the fixed performance loss caused by reflection, and on the other hand The performance loss is that when there are many structure type fields, traversal matching is required to obtain the corresponding content. The following is an analysis of performance based on a specific example of reflection:

Test reflection struct initialization

// 测试结构体初始化的反射性能func Benchmark_Reflect_New(b *testing.B) {   var tf *TestReflectField   t := reflect.TypeOf(TestReflectField{})   for i := 0; i < b.N; i++ {      tf = reflect.New(t).Interface().(*TestReflectField)   }   _ = tf}
// 测试结构体初始化的性能func Benchmark_New(b *testing.B) { var tf *TestReflectField for i := 0; i < b.N; i++ { tf = new(TestReflectField) } _ = tf}

operation result:

picture

It can be seen that there is a performance gap between using reflection to initialize a structure and directly using it to create a new structure, but the gap is not large, and the performance loss is less than double. It seems that the performance loss is not very large, which is acceptable.

Test struct field read/assign

// ---------    ------------  字段读  ----------- ----------- -----------// 测试反射读取结构体字段值的性能func Benchmark_Reflect_GetField(b *testing.B) {   var tf = new(TestReflectField)   var r int64   temp := reflect.ValueOf(tf).Elem()   for i := 0; i < b.N; i++ {      r = temp.Field(1).Int()   }   _ = tf   _ = r}
// 测试反射读取结构体字段值的性能func Benchmark_Reflect_GetFieldByName(b *testing.B) { var tf = new(TestReflectField) temp := reflect.ValueOf(tf).Elem() var r int64 for i := 0; i < b.N; i++ { r = temp.FieldByName("Age").Int() } _ = tf _ = r}
// 测试结构体字段读取数据的性能func Benchmark_GetField(b *testing.B) { var tf = new(TestReflectField) tf.Age = 1995 var r int for i := 0; i < b.N; i++ { r = tf.Age } _ = tf _ = r}
// --------- ------------ 字段写 ----------- ----------- -----------// 测试反射设置结构体字段的性能func Benchmark_Reflect_Field(b *testing.B) { var tf = new(TestReflectField)
temp := reflect.ValueOf(tf).Elem() for i := 0; i < b.N; i++ { temp.Field(1).SetInt(int64(25)) } _ = tf}
// 测试反射设置结构体字段的性能func Benchmark_Reflect_FieldByName(b *testing.B) { var tf = new(TestReflectField) temp := reflect.ValueOf(tf).Elem() for i := 0; i < b.N; i++ { temp.FieldByName("Age").SetInt(int64(25)) } _ = tf}
// 测试结构体字段设置的性能func Benchmark_Field(b *testing.B) { var tf = new(TestReflectField) for i := 0; i < b.N; i++ { tf.Age = i } _ = tf}

Test Results:

picture

picture

As can be seen from the above, the time-consuming of struct field reading through reflection is a hundred times that of direct reading. Assigning an instance variable directly takes 0.5 ns each time, and the performance is about 10 times that of specifying the location field of the instance through the reflection operation. The performance of using the FieldByName("Age") method is about ten times lower than that of using the Field(1). The position in the body, and then assign values ​​through the position, so the performance is much lower than using Field(index) directly.

Suggest:
1. If it is not necessary, try not to use reflection for operations. When using reflection, evaluate the impact of introducing reflection on interface performance. 

2. Reduce the use of the FieldByName method . When you need to use reflection to access member variables, use the member's ordinal number as much as possible. If you only know the name of the member variable, look at the usage scenario of the specific code. If you can get the serial number of the member through TypeOf(), Type.FieldByName() and StructField.Index in the startup phase or before frequent access. Note that what is needed here is to use reflect.Type instead of reflect.Value. You can't get the field name through reflect.Value.

Test struct method calls

// 测试通过结构体访问方法性能func BenchmarkMethod(b *testing.B) {   t := &TestReflectField{}   for i := 0; i < b.N; i++ {      t.Func0()   }}
// 测试通过序号反射访问无参数方法性能func BenchmarkReflectMethod(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { v.Method(0).Call(nil) }}
// 测试通过名称反射访问无参数方法性能func BenchmarkReflectMethodByName(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { v.MethodByName("Func0").Call(nil) }}
// 测试通过反射访问有参数方法性能func BenchmarkReflectMethod_WithArgs(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { v.Method(1).Call([]reflect.Value{reflect.ValueOf(i)}) }}
// 测试通过反射访问结构体参数方法性能func BenchmarkReflectMethod_WithArgs_Mul(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { v.Method(2).Call([]reflect.Value{reflect.ValueOf(TestReflectField{})}) }}
// 测试通过反射访问接口参数方法性能func BenchmarkReflectMethod_WithArgs_Interface(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { var tf TestInterface = &TestReflectField{} v.Method(3).Call([]reflect.Value{reflect.ValueOf(tf)}) }}
// 测试访问多参数方法性能func BenchmarkMethod_WithManyArgs(b *testing.B) { s := &TestReflectField{} for i := 0; i < b.N; i++ { s.Func4(i, i, i, i, i, i) }}
// 测试通过反射访问多参数方法性能func BenchmarkReflectMethod_WithManyArgs(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) va := make([]reflect.Value, 0) for i := 1; i <= 6; i++ { va = append(va, reflect.ValueOf(i)) }
for i := 0; i < b.N; i++ { v.Method(4).Call(va) }}
// 测试访问有返回值的方法性能func BenchmarkMethod_WithResp(b *testing.B) { s := &TestReflectField{} for i := 0; i < b.N; i++ { _ = s.Func5() }}
// 测试通过反射访问有返回值的方法性能func BenchmarkReflectMethod_WithResp(b *testing.B) { v := reflect.ValueOf(&TestReflectField{}) for i := 0; i < b.N; i++ { _ = v.Method(5).Call(nil)[0].Int() }}

picture

This test result is the same as the above analysis

Advantages and disadvantages:

advantage:

  1. Reflection improves the flexibility and expansibility of the program, reduces the coupling, and improves the adaptive ability.
  2. Reasonable use of reflection can reduce duplication of code
shortcoming:
  1. Code related to reflection is often difficult to read. In software engineering, code readability is also a very important indicator.

  2. Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。

  3. 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

反射在 okr 中的简单应用

func OkrBaseMW(next endpoint.EndPoint) endpoint.EndPoint {   return func(ctx context.Context, req interface{}) (resp interface{}, err error) {      if req == nil {         return next(ctx, req)      }      requestValue := reflect.ValueOf(req)
// 若req为指针,则转换为非指针值 if requestValue.Type().Kind() == reflect.Ptr { requestValue = requestValue.Elem() }
// 若req的值不是一个struct,则不注入 if requestValue.Type().Kind() != reflect.Struct { return next(ctx, req) }
if requestValue.IsValid() { okrBaseValue := requestValue.FieldByName("OkrBase") if okrBaseValue.IsValid() && okrBaseValue.Type().Kind() == reflect.Ptr { okrBase, ok := okrBaseValue.Interface().(*okrx.OkrBase) if ok { ctx = contextWithUserInfo(ctx, okrBase) ctx = contextWithLocaleInfo(ctx, okrBase) ctx = contextWithUserAgent(ctx, okrBase) ctx = contextWithCsrfToken(ctx, okrBase) ctx = contextWithReferer(ctx, okrBase) ctx = contextWithXForwardedFor(ctx, okrBase) ctx = contextWithHost(ctx, okrBase) ctx = contextWithURI(ctx, okrBase) ctx = contextWithSession(ctx, okrBase) } } }
return next(ctx, req) }}

结论:

使用反射必定会导致性能下降,但是反射是一个强有力的工具,可以解决我们平时的很多问题,比如数据库映射、数据序列化、代码生成场景。在使用反射的时候,我们需要避免一些性能过低的操作,例如使用 FieldByName() 和MethodByName() 方法,如果必须使用这些方法的时候,我们可以预先通过字段名或者方法名获取到对应的字段序号,然后使用性能较高的反射操作,以此提升使用反射的性能。

加入我们

我们来自字节跳动飞书商业应用研发部(Lark Business Applications),目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上,包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统,也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。

欢迎各位加入我们。扫码发现职位&投递简历(二维码如下)官网投递:
picture
👇 点击阅读原文,欢迎各位加入我们