前言
golang最近在中国非常火爆,尤其是后端服务开发场景。原生并发支持、优秀的性能、统一的风格,极大提升了开发效率。笔者用golang独立开发过不少小中型系统,写了几万行代码,确实很爽。
不过,统一的风格,也带来了一些问题。
从一个久远的争论说起There are only two hard things in Computer Science: cache invalidation and naming things. by Phil Karlton
计算机科学只有两大难题,命名占了一半。
腾讯QQ的程序员喜欢匈牙利命名法,比如szName,stUser,astUserList,bOk。在名字前面加上类型的标记,写起来很有安全感。
linux开发或许最喜欢下划线命名法(GNU编码风格),比如do_linuxrc,release_libc_mem。单词之间有下划线分隔,更易读。
还有人习惯于驼峰命名法,尤其是几年前的前端,因为jQuery全是这样的API,连自己都是。这种风格节约空间,易读性也不错。
到了golang这里,情况就变了。公共字段、函数、方法,都必须使用大写字母开头,为了可读性,基本上只能使用Pascal风格,如ListenAndServe。
笔者在编码时,是比较认可这种风格的。公有自定义类型、方法、函数和结构体字段,使用Pascal风格,私有内容用驼峰式,局部变量用小写,写代码很清爽。
但是在网络协议和数据库存储中,Pascal风格比较难受。一方面,每个字母都大写不符合英语阅读习惯,且英文单词间总是有空格,Pascal过于紧凑,不利于浏览协议、日志和数据
另一方面,在手敲协议或数据库语句时,每个字母都可能出现大写要按shift,主shift手经常同时按两个键。长期写代码的老油条一定都有这种感觉,左手按shift会导致手型变化,可能手腕会旋转,再去按字母键的话,效率比较低,且手腕更易磨损。
用下划线风格的好处,还不止这些。如果数据接入自然语言处理的话,只有下划线风格可以方便地获得关键词
搜索系统同理
在使用文本查找的方式阅览代码或数据库时,通常不区分大小写,其他风格会出现很多跨词结果,造成干扰
……
不仅适合阅读,提升效率,便于扩展,甚至还能避免一些健康风险。
所以,在数据库和网络协议上,下划线命名法才是首选。
那么,用go语言时,如何让struct字段变成下划线风格呢?
原生的JSON字段命名方式
golang在默认情况下,json.Marshal的结果就是字段名,开发者也可以通过json tag来自定义字段名。
type Student struct {
Name string `json:"name"`
MathScore int `json:"math_score"`
StudentNO string `json:"student_no"`
}
这很好,且没有性能损失。只是多写了几个字而已。
对于一个只包含三五个,十个八个struct的系统而言,多写几行代码不成问题。但一个有几十个上百个struct的业务,也要一个一个写过来吗?
就算你敢写,我也不敢用。机械化重复的工作,人力太不可靠。执行的人可能出错,找人检查一样可能出错。几千条配置,还可能继续增加,完全依赖手写?太危险了。
朴素自动化方案
代码生成器
通过“某种方式”,获取代码中的全部结构体,自动生成设置了tag的新代码,再编译。
这种方式运行时效率是最高的,但是真的可行吗?首先,go并未提供直接获取包中所有结构体的原生方法,所以只能自己做代码解析。
其次,并不是所有结构体都是type X struct开头的简单模式。在go中,匿名结构体有很多漂亮的用法,比如快速实现JSON数据的平铺组装。为了适配struct的各种场景,不得不做更深入的解析。
最后,代码生成器作为外部工具,很难管理生效范围。项目依赖外部包是否也要使用此法生成?如何界定哪里应该使用转换,哪里不用?随着项目的膨胀,这将会是一场灾难。
成本高,配置复杂,是其硬伤。
笔者曾使用go-protobuf来部分解决此问题,需要单独管理proto文件,在makefile中处理生成逻辑。后来需要对bson也照此处理,不得不去修改pb源码才支持。虽然省了手写tag,但依然要手写pb。每个新项目还要带着一坨定制环境。
非常难受。
修改JSON包
另一个直观的方式是修改json包。如无tag指定,golang默认使用代码中的字段名,在这里加一个逻辑,变成自己想要的风格,不就行了吗?
当然行了!而且开发成本和运行成本,都非常低!