Golang中的坑二
for ...range
最近两周用Golang做项目,编写web服务,两周时间写了大概五千行代码(业务代码加单元测试用例代码)。用Go的感觉很爽,编码效率高,运行效率也不错,用了beego,avro,xorm,反射。今天和前端联调遇到了一个bug,发现踩到了第二个坑。踩坑不怕,踩过一次就不会再犯了,这就是实践的好处。
坑是这样的:数据采用avro描述,用xorm存取到mysql;对于有嵌套的数据结构,avro生成的go结构体以指针切片的形式声明,xorm Find方法采用了结构体切片的形式。有如下avro schema:
{
"fields": [
{
"name": "Oid",
"type": "int"
},
{
"name": "name",
"type": "string"
},
{
"name": "inner_objs",
"type": {
"items": {
"fields": [
{
"name": "Iid",
"type": "int"
},
{
"name": "name",
"type": "string"
},
{
"name": "degree",
"type": "int"
}
],
"name": "inner_obj",
"type": "record"
},
"type": "array"
}
}
],
"name": "outer_obj",
"type": "record"
}
对应的go结构体及数据操作函数如下:
package models
type OuterObj struct {
Oid int32 `json:"oid" xorm:"pk notnull"`
Name string `json:"name"`
InnerObjs []*InnerObj `json:"inner_objs"`
}
type InnerObj struct {
Oid int32 `json:"-" xorm:"pk notnull"`
Iid int32 `json:"iid" xorm:"pk notnull"`
Name string `json:"name"`
Degree int32 `json:"degree"`
}
func GetOuterObjs(index int, count int) (objs []OuterObj, err error) {
objs = make([]OuterObj, 0)
err = x_intellitbi.Asc("oid").Limit(count, (index-1)*count).Find(&objs)
if err != nil {
return
}
for index := 0; index < len(objs); index++ {
objs[index].InnerObjs = make([]*InnerObj, 0)
innerObjs := make([]InnerObj, 0)
err = x_intellitbi.Where("oid=?", objs[index].Oid).Find(&innerObjs)
if err != nil {
return
}
for _, v := range innerObjs {
objs[index].InnerObjs = append(objs[index].InnerObjs, &v)
}
}
Return
}
GetOuterObjs返回的结果不符合预期,每个OuterObj实例内的InnerObjs对应的内容全部相同。
潜意识里,肯定有一部分人对带有短变量声明(short variable declaration :=)的for...range的理解是:每次迭代声明一个不同的变量。用Google搜了下,在2011年golang-nuts上确实有过关于该问题的讨论(https://groups.google.com/forum/#!topic/golang-nuts/e08r1Vk7ufQ)。
继续查看golang spec For statements with range clause 部分
最新的spec中有说明:
The iteration variables may be declared by the "range" clause using a form of short variable declaration (:=). In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement; they are re-used in each iteration. If the iteration variables are declared outside the "for" statement, after execution their values will be those of the last iteration.
翻译如下:
迭代变量可以由“range”子句使用一个短变量声明(:=)形式声明。在这种情况下,它们的类型设置为相应迭代值的类型,其范围是“for”语句的块;它们在每次迭代中被重用。如果迭代变量在“for”语句之外被声明,执行后它们的值将是上一次迭代的值。
用:=声明的迭代变量在每次迭代过程中被重用了,应该可以理解成在for作用域内声明了迭代变量,在for内可见,每次迭代过程中被重新评估值;与在for外部声明迭代变量类似,区别是作用域不同。
另外再说下xorm,InsertMulti采用了指针切片,Find采用了结构体切片的指针,如果Find统一成指针切片就更好了,这样也免得多一层转换。