1. Graphic reflection

This article The Laws of Reflection must be read before using Reflection. There are many Chinese translation versions on the Internet, which can be searched and read.

Before getting into the details, let’s look at the three principles of reflection:

  • Reflection goes from interface value to reflection object.
  • Reflection goes from reflection object to interface value.
  • To modify a reflection object, the value must be settable.

In the three principles, there are two key words interface value and Reflection Object. It’s a little hard to understand, but maybe if YOU draw a picture.

What is reflection Object? There are many reflection objects, but the two most critical reflection objects are Reflect.type and Reflect.value. To put it bluntly, it is the abstract definition class of the type and value of a variable, or the class definition of the meta-information of a variable.

So why is it interface value, not variable value or object value? Because the latter two are not extensive. In the Go language, the empty interface{} is a generic type that can be used as a value of any type. The value of interface{} value is the empty value of interface{} value.

Combined with the diagram, the three principles of reflection can be summarized into one sentence:

Reflection can realize the mutual derivation and transformation between Reflection Object and interface value. If the value of an object variable can be modified through reflection, the premise is that the object variable itself can be modified.

2. Application of reflection

The criteria for determining whether to use reflection in program development is simple, that is, whether to use the type information of variables. This is not hard to judge, but how to use reflection properly is the hard part. Because reflection, unlike ordinary function functions, is costly to program performance, it should be avoided in high frequency operations.

Here are a few examples of reflection application scenarios:

2.1 Determine whether the unknown object implements a specific interface

In general, it is easy to determine whether an unknown object implements a specific interface by using type validation of the variable name (interface name). The exception, however, is when the calling code is checked in the framework code implementation. Because the framework code is implemented first and then called, it cannot be verified in the framework code through simple type-validation.

Just look at GRPC’s server-side registration interface.

GrpcServer: = GRPC NewServer () / / pb. The implementation of a service registration RegisterRouteGuideServer (grpcServer, & routeGuideServer {})Copy the code

The program reports an error when the registered implementation does not implement all service interfaces. It is how to do, can directly see pb. RegisterRouteGuideServer implementation code. Here is a simple code, the principle is the same:

Type Foo interface {Bar(int)} DST := (*Foo)(nil) dstType := reflect.typeof (DST).elem () // Verifies whether the unknown variable SRC implements Foo SrcType := reflect.TypeOf(SRC) if! srcType.Implements(dstType) { log.Fatalf("type %v that does not satisfy %v", srcType, dstType) }Copy the code

This is also the basic implementation of the GRPC framework, since this code is usually at startup and has no impact on application performance.

2.2 Structure field attribute tag

Usually, when defining a structure to be parsed by JSON, the specific field attributes in the structure are set with the tag tag. The auxiliary information of the tag corresponds to the field name of the specific JSON string. JSON parsing is not provided as an example, and usually JSON parsing code is applied to the request response phase, which is not the best scenario for reflection, but it has to be done in the business.

Here I’m going to cite another example of reflection using the structure field attribute tag, which I think is the most perfect example of reflection and is really recommended. This example appeared in the open source project github.com/jaegertracing/jaeger-lib.

Those of you who have used Prometheus know that metric detection scalars need to be defined and registered by:

var ( // Create a summary to track fictional interservice RPC latencies for three // distinct services with different latency distributions. These services are // differentiated via a "service" label. rpcDurations = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "rpc_durations_seconds", Help: Objectives: map[float64]float64{0.1:0.05, 0.9:0.01, 0.99: // The same as above, but now as a histogram, and only for the normal // distribution. The buckets are targeted to the parameters of the // normal distribution, with 20 buckets centered on the mean, each // half-sigma wide. rpcDurationsHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "rpc_durations_histogram_seconds", Help: "RPC latency distributions.", Buckets: prometheus.LinearBuckets(*normMean-5**normDomain, .5**normDomain, 20), }) ) func init() { // Register the summary and the histogram with Prometheus's default registry. prometheus.MustRegister(rpcDurations) prometheus.MustRegister(rpcDurationsHistogram) // Add Go module build info. prometheus.MustRegister(prometheus.NewBuildInfoCollector()) }Copy the code

This is an example from Prometheus/Client_golang, which has a lot of code and requires the init function. Once a project is complex, the readability is poor. Look at the way github.com/jaegertracing/jaeger-lib/metrics offers:

type App struct{ //attributes ... //metrics ... metrics struct{ // Size of the current server queue QueueSize metrics.Gauge `metric:"thrift.udp.server.queue_size"` // Size (in bytes) of packets received by server PacketSize metrics.Gauge `metric:"thrift.udp.server.packet_size"` // Number of packets dropped by server PacketsDropped metrics.Counter `metric:"thrift.udp.server.packets.dropped"` // Number of packets processed by server PacketsProcessed metrics.Counter `metric:"thrift.udp.server.packets.processed"` //  Number of malformed packets the server received ReadError metrics.Counter `metric:"thrift.udp.server.read.errors"` } }Copy the code

In the application, the anonymous structure metrics is directly defined, and the metric detection scalar for the application is defined in the specific structure field, and the name is set by the field tag. This greatly enhances the readability of the code.

Now look at the initialization code:

The import "github.com/jaegertracing/jaeger-lib/metrics/prometheus" / / initializes the metrics. The Init (& app. The metrics, Prometheus. New (), nil)Copy the code

No, it’s perfect. This sample code is implemented in my project: X-mod /thriftudp, which is written entirely in reference to the library implementation.

2.3 Function Adaptation

In the original exercise, I wrote a piece of function adaptation code, using reflection. Stick the:

//Executor ADAPTS to target interface by adding context. context parameter type Executor func(CTX context. context, args... Func Adapter(fn interface{}) Executor {if fn! = nil && reflect.TypeOf(fn).Kind() == reflect.Func { return func(ctx context.Context, args ... interface{}) { fv := reflect.ValueOf(fn) params := make([]reflect.Value, 0, len(args)+1) params = append(params, reflect.ValueOf(ctx)) for _, arg := range args { params = append(params, reflect.ValueOf(arg)) } fv.Call(params) } } return func(ctx context.Context, args ... interface{}) { log.Warn("null executor implemention") } }Copy the code

Just for practice, production environment is still not recommended, feel too heavy.

I recently took a look at the Go 1.14 proposal regarding the introduction of the try keyword, try reference. Based on the functionality shown, if you implement it yourself, you should use reflection. So for now so rely on error check function implementation, whether appropriate, quite doubtful, wait for Go 1.14 out, verify.

3 summary

The best application scenario of reflection is the startup phase of the program, which implements some pre-work such as type checking and registration, without affecting the performance of the program and increasing the readability of the code. These days I’m crazy about new pants, so stop asking me what reflection is 🙂

Reference Resources:

  • The Laws of Reflection
  • Go Data Structures: Interfaces