『InfoQ』Go 语言的微吐槽,这是一篇实践者对( 三 )


funcGetCustomers(db*sql.DB)([]*Customer,error){rows,err:=db.Query("SELECTname,emailFROMcustomers")iferr!=nil{returnnil,err}
varcustomers[]*Customerfor_,row:=rangerows{customers=append(customers,&User{Name:row[0].(string)Email:row[1].(string)})}
returncustomers,nil}
这是相当简单的 , 使用这个服务的HTTP控制器如下所示:
packagecontrollers
import"http"import"encoding/json"import"github.com/me/myapp/models"
funcGetCustomers(req*http.Request,resphttp.ResponseWriter){...customers,err:=models.GetCustomers(db)iferr!=nil{...}resp.WriteHeader(200)iferr:=json.NewEncoder(resp).Encode(customers);err!=nil{...}}
这些都是基础 , 但这里实际上有问题 , 它可能会在这个API的消费者中触发错误 。 当数据库中没有客户时 , SQL查询将不返回任何行 。 因此 , 附加到customers切片的循环将永远不会处理任何项目 。 于是 , custormers切片将作为nil返回 。
当JSON编码器看到一个nil切片时 , 它将对响应写入null , 而不是写入[] , 可是没有结果的情况下本来应该写入的是后者 , 这势必会给API消费者带来一些问题 , 因为在没有项目的情况下它们本来预期的是一个空列表 。
解决方案很简单 , 要么使用一个切片字面量customers:=[]*Customer{} , 要么使用customers:=make([]*Customer,0)这样的调用 。 请注意 , 某些Golinters会警告你不要使用空切片字面量 , 并建议使用varcustomers[]*Customer来代替 , 但后者的语义是不一样的 。
在其他地方也可能出现麻烦 。 对于len函数 , 一个空映射和一个nil映射是相同的 。 他们有0个元素 。 但是对于其他函数 , 例如reflect.DeepEqual来说 , 这些映射并不相同 。 我认为考虑到len的行为方式 , 如果一个函数会检查这两个映射是否相同 , 那么可以预期检查的结果是一样的 。 但是reflect.DeepEqual表示不同意 , 这可能因为它使用了反射来对比两个对象 , 这种比法不是很好的办法 , 但却是Go目前唯一可用的选项 。
Go模块和Gitlab
一开始 , 依靠Git存储库下载模块可能是一个好主意 , 但是一旦出现更复杂的用例 , Go模块就会彻底瓦解 。 我的团队在搭配使用Go模块和私有Gitlab实例时遇到了很多问题 。 其中有两大问题最为突出 。
第一个问题是Gitlab允许用户拥有递归项目组 。 例如 , 你可以在gitlab.whatever.com/group/tools/tool-1上拥有一个git存储库 。 Go模块并没有对此提供开箱即用的支持 。 Go模块将尝试下载gitlab.whatever.com/group/tools.git , 因为它假定该网站使用类似于GitHub的模式 , 也就是说里面只有两个级别的嵌套 。 我们必须在go.mod文件中使用一个replace来将Go模块指向正确的位置 。
还有一种解决问题的方法是使用HTML标签 , 让它指向正确的git存储库 , 但这需要Git平台来支持它 。 要求Git平台为Go模块添加这种特殊用例的支持并不是一个好的设计决策 。 它不仅需要在Git平台中进行上游更改 , 而且还需要将已部署的软件升级到最新版本 , 后者在企业部署流程中并不会一直那么迅速 。
第二个问题是 , 由于我们的Gitlab实例是私有的 , 并且Go尝试通过https下载git存储库 , 因此当我们尝试下载未经任何身份验证的Go模块时 , 会收到401错误 。 使用我们的Gitlab密码进行身份验证是不切实际的选择 , 尤其是在涉及CI/CD的情况下 。 我们找到的解决方案是在使用这个.gitconfig发出https请求时 , 强制git使用ssh 。
[url"git@gitlab.whatever.com:"]insteadOf=https://gitlab.whatever.com
这个方案在实践中效果很好 , 但是在初次遇到这个问题时要修复它没那么容易 。 它还假定SSH公钥已在Gitlab中注册 , 并且私钥未使用密码加密 。 如果你在GNOMEKeyring或KDEWallet之类的keyring代理中注册了密码 , 并且git集成了它 , 那么倒可能使用一个加密的私钥 , 但是我没有尝试过这种办法 , 所以也不知道是否真的可行 。