本文同時(shí)發(fā)布在個(gè)人博客chinazt.cc 和 gitbook

今日看到了一個(gè)有趣的golang項(xiàng)目--kolpa(https://github.com/malisit/kolpa)。 這個(gè)項(xiàng)目可以用來(lái)生成偽造的姓名,地址,時(shí)間,User-Agent等等信息,在需要大量隨機(jī)數(shù)據(jù)的測(cè)試環(huán)境中非常合適。

點(diǎn)擊fork之后,放在本地環(huán)境中build,run結(jié)果失敗。運(yùn)行項(xiàng)目中提供的demo也失敗,按道理來(lái)說(shuō)官方提供的demo應(yīng)該都會(huì)成功,而且自己也沒(méi)有修改任何一行代碼,失敗是不科學(xué)的。

所以只能剖解代碼,查找失敗原因。

一查不知道,原來(lái)此項(xiàng)目需要依賴各個(gè)語(yǔ)言環(huán)境下的模板文件,而模板文件都放在項(xiàng)目的data目錄中。 在代碼中,通過(guò)硬編碼來(lái)確定模板文件位置:

// Reads the file "fName" and returns its content as a slice of strings.func (g *Generator) fileToSlice(fName string) ([]string, error) {var res []stringpath := os.Getenv("GOPATH") + "/src/github.com/malisit/kolpa/data/" + g.Locale + "/" + fName
file, err := os.Open(path)if err != nil {return nil, err
}defer file.Close()

scanner := bufio.NewScanner(file)for scanner.Scan() {
res = append(res, scanner.Text())
}if err := scanner.Err(); err != nil {//log.Println("Inteded generation is not valid for selected language. Switching to en_US.")return g.fileToSlice(fName)
}return res, nil}

因?yàn)槲沂峭ㄟ^(guò)fork,然后clone到本地的方式來(lái)運(yùn)行demo,本地此時(shí)packge路徑已經(jīng)不是上面的路徑了,所以導(dǎo)致運(yùn)行失敗。 很難說(shuō)這是一個(gè)bug,但的確影響到了程序運(yùn)行。 所以說(shuō)不良的代碼風(fēng)格更為恰當(dāng)吧。

既然找到了問(wèn)題,那下一步就是如何解決問(wèn)題。 應(yīng)該如何在Runtime時(shí)實(shí)時(shí)獲取package位置呢?

說(shuō)到Runtime,那么一定就少不了Reflect package。

Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. The typical use is to take a value with static type interface{} and extract its dynamic type information by calling TypeOf, which returns a Type.

從Reflect的介紹上看,Reflect package推薦的使用方式是通過(guò)TypeOf返回一個(gè)帶有interface{}所有動(dòng)態(tài)類型信息的Type類型,然后通過(guò)Type類型來(lái)獲取各種程序需要的信息。

type Type interface {// Align returns the alignment in bytes of a value of// this type when allocated in memory.Align() int// FieldAlign returns the alignment in bytes of a value of// this type when used as a field in a struct.FieldAlign() int// Method returns the i'th method in the type's method set.// It panics if i is not in the range [0, NumMethod()).//// For a non-interface type T or *T, the returned Method's Type and Func// fields describe a function whose first argument is the receiver.//// For an interface type, the returned Method's Type field gives the// method signature, without a receiver, and the Func field is nil.Method(int) Method// MethodByName returns the method with that name in the type's// method set and a boolean indicating if the method was found.//// For a non-interface type T or *T, the returned Method's Type and Func// fields describe a function whose first argument is the receiver.//// For an interface type, the returned Method's Type field gives the// method signature, without a receiver, and the Func field is nil.MethodByName(string) (Method, bool)// NumMethod returns the number of exported methods in the type's method set.NumMethod() int// Name returns the type's name within its package.// It returns an empty string for unnamed types.Name() string// PkgPath returns a named type's package path, that is, the import path// that uniquely identifies the package, such as "encoding/base64".// If the type was predeclared (string, error) or unnamed (*T, struct{}, []int),// the package path will be the empty string.PkgPath() string// Size returns the number of bytes needed to store// a value of the given type; it is analogous to unsafe.Sizeof.Size() uintptr// String returns a string representation of the type.// The string representation may use shortened package names// (e.g., base64 instead of "encoding/base64") and is not// guaranteed to be unique among types. To test for type identity,// compare the Types directly.String() string// Kind returns the specific kind of this type.Kind() Kind// Implements reports whether the type implements the interface type u.Implements(u Type) bool// AssignableTo reports whether a value of the type is assignable to type u.AssignableTo(u Type) bool// ConvertibleTo reports whether a value of the type is convertible to type u.ConvertibleTo(u Type) bool// Comparable reports whether values of this type are comparable.Comparable() bool// Bits returns the size of the type in bits.// It panics if the type's Kind is not one of the// sized or unsized Int, Uint, Float, or Complex kinds.Bits() int// ChanDir returns a channel type's direction.// It panics if the type's Kind is not Chan.ChanDir() ChanDir// IsVariadic reports whether a function type's final input parameter// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's// implicit actual type []T.//// For concreteness, if t represents func(x int, y ... float64), then////  t.NumIn() == 2//  t.In(0) is the reflect.Type for "int"//  t.In(1) is the reflect.Type for "[]float64"//  t.IsVariadic() == true//// IsVariadic panics if the type's Kind is not Func.IsVariadic() bool// Elem returns a type's element type.// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.Elem() Type// Field returns a struct type's i'th field.// It panics if the type's Kind is not Struct.// It panics if i is not in the range [0, NumField()).Field(i int) StructField// FieldByIndex returns the nested field corresponding// to the index sequence. It is equivalent to calling Field// successively for each index i.// It panics if the type's Kind is not Struct.FieldByIndex(index []int) StructField// FieldByName returns the struct field with the given name// and a boolean indicating if the field was found.FieldByName(name string) (StructField, bool)// FieldByNameFunc returns the struct field with a name// that satisfies the match function and a boolean indicating if// the field was found.//// FieldByNameFunc considers the fields in the struct itself// and then the fields in any anonymous structs, in breadth first order,// stopping at the shallowest nesting depth containing one or more// fields satisfying the match function. If multiple fields at that depth// satisfy the match function, they cancel each other// and FieldByNameFunc returns no match.// This behavior mirrors Go's handling of name lookup in// structs containing anonymous fields.FieldByNameFunc(match func(string) bool) (StructField, bool)// In returns the type of a function type's i'th input parameter.// It panics if the type's Kind is not Func.// It panics if i is not in the range [0, NumIn()).In(i int) Type// Key returns a map type's key type.// It panics if the type's Kind is not Map.Key() Type// Len returns an array type's length.// It panics if the type's Kind is not Array.Len() int// NumField returns a struct type's field count.// It panics if the type's Kind is not Struct.NumField() int// NumIn returns a function type's input parameter count.// It panics if the type's Kind is not Func.NumIn() int// NumOut returns a function type's output parameter count.// It panics if the type's Kind is not Func.NumOut() int// Out returns the type of a function type's i'th output parameter.// It panics if the type's Kind is not Func.// It panics if i is not in the range [0, NumOut()).Out(i int) Type// contains filtered or unexported methods}

在Type接口定義中,和Package Path有關(guān)的有兩個(gè)函數(shù):

  • PkgPath()

  • String()

PkgPath 返回指定類型的import package path,也就是說(shuō),如果代碼中有import encoding/base64這樣的語(yǔ)句, 那么通過(guò)PkgPath()就會(huì)返回encoding/base64,而不是base64package所在的實(shí)際路徑。 反言之,PkgPath()返回的是import package path。

而如果繼續(xù)使用上例中的encoding/base64來(lái)說(shuō),String()返回的是base64,而不是encoding/base64。 String()返回的是實(shí)際使用的package name。

所以總結(jié)如下:

  • PkgPath() 返回import package path

  • String() 返回import package name

顯然,PkgPath()更適合我們的需求。 簡(jiǎn)單改造代碼如下:

1.增加 Pkg path

// Generator struct to access various generator functionstype Generator struct {
Locale stringPkg string}

2.獲取Pkg

// C is the creator function, initiates kolpa with or without locale// setting. The default locale setting is "en_US".// Returns a generator type that will be used to call generator methods.func C(localeVar ...string) Generator {
newGenerator := Generator{}if len(localeVar) > 0 {
newGenerator.Locale = localeVar[0]
} else {
newGenerator.Locale = "en_US"}// newGenerator.populateFunctions()newGenerator.Pkg = reflect.TypeOf(newGenerator).PkgPath()return newGenerator
}

3.替換硬編碼

// Reads the file "fName" and returns its content as a slice of strings.func (g *Generator) fileToSlice(fName string) ([]string, error) {var res []stringpath := os.Getenv("GOPATH") + "/src/" + g.Pkg + "/data/" + g.Locale + "/" + fName
file, err := os.Open(path)if err != nil {return nil, err
}defer file.Close()

scanner := bufio.NewScanner(file)for scanner.Scan() {
res = append(res, scanner.Text())
}if err := scanner.Err(); err != nil {//log.Println("Inteded generation is not valid for selected language. Switching to en_US.")return g.fileToSlice(fName)
}return res, nil}

4.運(yùn)行demo

?> ~/S/g/g/s/t/kopla ./kopla
石潔玉
幸和平

完美生成兩個(gè)隨機(jī)姓名,話說(shuō)"和平"也是一代人經(jīng)常起的名字。

最后提一句,有的地方曾經(jīng)說(shuō)到盡量少使用Reflect package。 因此反射使用多了,影響效率。 我想這種想法應(yīng)該是從Java VM那里流傳出來(lái)的吧。 因?yàn)镴ava VM負(fù)責(zé)將字節(jié)碼翻譯成機(jī)器碼,因此頻繁調(diào)用反射會(huì)加重VM切換上下文的代價(jià),也就是把裝載期做的事情搬到了運(yùn)行期,勢(shì)必降低運(yùn)行效率。 golang的反射減少了翻譯環(huán)節(jié),同時(shí)借助于編譯器進(jìn)行了代碼優(yōu)化,雖然同樣在反射時(shí)需要進(jìn)行額外的安全檢查和類型檢查,但不會(huì)降低太多效率。 而且上面,我們也看到只有在調(diào)用時(shí)也只發(fā)生了一次反射調(diào)用,影響幾乎可忽略不計(jì)。

如果您認(rèn)為此文章對(duì)您有所幫助,請(qǐng)您點(diǎn)擊推薦。進(jìn)步來(lái)源于思想的碰撞,所以歡迎大家踴躍留言評(píng)論。

http://www.cnblogs.com/vikings-blog/p/7131618.html